diff --git a/.gitignore b/.gitignore index 1be66d6..4d75a13 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ ## User settings xcuserdata/ +LiveRecipes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcuserstate @@ -41,7 +42,7 @@ playground.xcworkspace # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins -# Package.resolved +Package.resolved # *.xcodeproj # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata diff --git a/LiveRecipes.xcodeproj/project.pbxproj b/LiveRecipes.xcodeproj/project.pbxproj index 3c5d250..634de09 100644 --- a/LiveRecipes.xcodeproj/project.pbxproj +++ b/LiveRecipes.xcodeproj/project.pbxproj @@ -21,6 +21,19 @@ 745DA4D72BB862D500BE4C5D /* OneDishAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745DA4D62BB862D500BE4C5D /* OneDishAssembly.swift */; }; 745DA4D92BB862EC00BE4C5D /* OneDishProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745DA4D82BB862EC00BE4C5D /* OneDishProtocols.swift */; }; 745DA4DB2BC889DD00BE4C5D /* OneStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 745DA4DA2BC889DD00BE4C5D /* OneStepView.swift */; }; + 74BF32C92BEF92B100C946D5 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74BF32C82BEF92B100C946D5 /* WidgetKit.framework */; }; + 74BF32CB2BEF92B100C946D5 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74BF32CA2BEF92B100C946D5 /* SwiftUI.framework */; }; + 74BF32CE2BEF92B100C946D5 /* TimerActivityWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32CD2BEF92B100C946D5 /* TimerActivityWidgetBundle.swift */; }; + 74BF32D02BEF92B100C946D5 /* TimerActivityWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32CF2BEF92B100C946D5 /* TimerActivityWidgetLiveActivity.swift */; }; + 74BF32D22BEF92B100C946D5 /* TimerActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32D12BEF92B100C946D5 /* TimerActivityWidget.swift */; }; + 74BF32D42BEF92B100C946D5 /* AppIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32D32BEF92B100C946D5 /* AppIntent.swift */; }; + 74BF32D62BEF92B300C946D5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 74BF32D52BEF92B300C946D5 /* Assets.xcassets */; }; + 74BF32DA2BEF92B300C946D5 /* TimerActivityWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 74BF32C72BEF92B100C946D5 /* TimerActivityWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 74BF32E02BEF933C00C946D5 /* LiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32DF2BEF933C00C946D5 /* LiveActivityAttributes.swift */; }; + 74BF32E72BF167DA00C946D5 /* LiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32DF2BEF933C00C946D5 /* LiveActivityAttributes.swift */; }; + 74BF32E92BF1687A00C946D5 /* CustomTimerViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32E82BF1687A00C946D5 /* CustomTimerViewStyle.swift */; }; + 74BF32EA2BF1687A00C946D5 /* CustomTimerViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BF32E82BF1687A00C946D5 /* CustomTimerViewStyle.swift */; }; + 74BF32EC2BF16B2600C946D5 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0EA6C632BB85AEC00995C49 /* String+Localized.swift */; }; 74D1B8542BD7BED300F317A9 /* PrepareForCookingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D1B8532BD7BED300F317A9 /* PrepareForCookingView.swift */; }; 74D1B8582BD8688600F317A9 /* TransperentBlur.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D1B8572BD8688600F317A9 /* TransperentBlur.swift */; }; 74D1B85B2BD9443400F317A9 /* StartCookingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74D1B85A2BD9443400F317A9 /* StartCookingButton.swift */; }; @@ -105,6 +118,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 74BF32D82BEF92B300C946D5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C0EA6C1C2BB85A9100995C49 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 74BF32C62BEF92B100C946D5; + remoteInfo = TimerActivityWidgetExtension; + }; C0EA6C3A2BB85A9200995C49 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C0EA6C1C2BB85A9100995C49 /* Project object */; @@ -121,6 +141,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 74BF32DE2BEF92B300C946D5 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 74BF32DA2BEF92B300C946D5 /* TimerActivityWidgetExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 660E20592BC6D1FB00BB23AA /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = ""; }; 66266C6C2BD2F6D300DE08E6 /* CookToTimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookToTimeView.swift; sourceTree = ""; }; @@ -136,6 +170,18 @@ 745DA4D62BB862D500BE4C5D /* OneDishAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneDishAssembly.swift; sourceTree = ""; }; 745DA4D82BB862EC00BE4C5D /* OneDishProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneDishProtocols.swift; sourceTree = ""; }; 745DA4DA2BC889DD00BE4C5D /* OneStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneStepView.swift; sourceTree = ""; }; + 74BF32C72BEF92B100C946D5 /* TimerActivityWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TimerActivityWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 74BF32C82BEF92B100C946D5 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 74BF32CA2BEF92B100C946D5 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 74BF32CD2BEF92B100C946D5 /* TimerActivityWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerActivityWidgetBundle.swift; sourceTree = ""; }; + 74BF32CF2BEF92B100C946D5 /* TimerActivityWidgetLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerActivityWidgetLiveActivity.swift; sourceTree = ""; }; + 74BF32D12BEF92B100C946D5 /* TimerActivityWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerActivityWidget.swift; sourceTree = ""; }; + 74BF32D32BEF92B100C946D5 /* AppIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntent.swift; sourceTree = ""; }; + 74BF32D52BEF92B300C946D5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 74BF32D72BEF92B300C946D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 74BF32DF2BEF933C00C946D5 /* LiveActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityAttributes.swift; sourceTree = ""; }; + 74BF32E22BEFD61000C946D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 74BF32E82BF1687A00C946D5 /* CustomTimerViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimerViewStyle.swift; sourceTree = ""; }; 74D1B8532BD7BED300F317A9 /* PrepareForCookingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepareForCookingView.swift; sourceTree = ""; }; 74D1B8572BD8688600F317A9 /* TransperentBlur.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransperentBlur.swift; sourceTree = ""; }; 74D1B85A2BD9443400F317A9 /* StartCookingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartCookingButton.swift; sourceTree = ""; }; @@ -222,6 +268,15 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 74BF32C42BEF92B100C946D5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 74BF32CB2BEF92B100C946D5 /* SwiftUI.framework in Frameworks */, + 74BF32C92BEF92B100C946D5 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C0EA6C212BB85A9100995C49 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -260,6 +315,40 @@ path = OneDish; sourceTree = ""; }; + 74BF32A32BEF88A600C946D5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 74BF32C82BEF92B100C946D5 /* WidgetKit.framework */, + 74BF32CA2BEF92B100C946D5 /* SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 74BF32CC2BEF92B100C946D5 /* TimerActivityWidget */ = { + isa = PBXGroup; + children = ( + 74BF32CD2BEF92B100C946D5 /* TimerActivityWidgetBundle.swift */, + 74BF32CF2BEF92B100C946D5 /* TimerActivityWidgetLiveActivity.swift */, + 74BF32D12BEF92B100C946D5 /* TimerActivityWidget.swift */, + 74BF32D32BEF92B100C946D5 /* AppIntent.swift */, + 74BF32D52BEF92B300C946D5 /* Assets.xcassets */, + 74BF32D72BEF92B300C946D5 /* Info.plist */, + ); + path = TimerActivityWidget; + sourceTree = ""; + }; + 74BF32E62BF163F800C946D5 /* Components */ = { + isa = PBXGroup; + children = ( + 745DA4DA2BC889DD00BE4C5D /* OneStepView.swift */, + 74D1B8532BD7BED300F317A9 /* PrepareForCookingView.swift */, + 74D1B8622BD9453700F317A9 /* NoStepsView.swift */, + 74BF32DF2BEF933C00C946D5 /* LiveActivityAttributes.swift */, + 74BF32E82BF1687A00C946D5 /* CustomTimerViewStyle.swift */, + ); + path = Components; + sourceTree = ""; + }; 74D1B8592BD943B100F317A9 /* AnimatedElements */ = { isa = PBXGroup; children = ( @@ -278,7 +367,9 @@ C0EA6C262BB85A9100995C49 /* LiveRecipes */, C0EA6C3C2BB85A9200995C49 /* LiveRecipesTests */, C0EA6C462BB85A9200995C49 /* LiveRecipesUITests */, + 74BF32CC2BEF92B100C946D5 /* TimerActivityWidget */, C0EA6C252BB85A9100995C49 /* Products */, + 74BF32A32BEF88A600C946D5 /* Frameworks */, ); sourceTree = ""; }; @@ -288,6 +379,7 @@ C0EA6C242BB85A9100995C49 /* LiveRecipes.app */, C0EA6C392BB85A9200995C49 /* LiveRecipesTests.xctest */, C0EA6C432BB85A9200995C49 /* LiveRecipesUITests.xctest */, + 74BF32C72BEF92B100C946D5 /* TimerActivityWidgetExtension.appex */, ); name = Products; sourceTree = ""; @@ -295,6 +387,7 @@ C0EA6C262BB85A9100995C49 /* LiveRecipes */ = { isa = PBXGroup; children = ( + 74BF32E22BEFD61000C946D5 /* Info.plist */, D02D7EE82BCD79300056DC33 /* FileManager */, D0596F912BC7FBB800A1BF50 /* DB */, C0EA6C642BB85AEC00995C49 /* ApplicationViewBuilder.swift */, @@ -403,15 +496,13 @@ C0EA6C712BB85AEC00995C49 /* Cooking */ = { isa = PBXGroup; children = ( + 74BF32E62BF163F800C946D5 /* Components */, 74D1B8592BD943B100F317A9 /* AnimatedElements */, C0EA6C722BB85AEC00995C49 /* CookingView.swift */, C0EA6C732BB85AEC00995C49 /* CookingViewModel.swift */, C0EA6C742BB85AEC00995C49 /* CookingProtocols.swift */, C0EA6C752BB85AEC00995C49 /* CookingAssembly.swift */, C0EA6C762BB85AEC00995C49 /* CookingModel.swift */, - 745DA4DA2BC889DD00BE4C5D /* OneStepView.swift */, - 74D1B8532BD7BED300F317A9 /* PrepareForCookingView.swift */, - 74D1B8622BD9453700F317A9 /* NoStepsView.swift */, ); path = Cooking; sourceTree = ""; @@ -578,6 +669,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 74BF32C62BEF92B100C946D5 /* TimerActivityWidgetExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 74BF32DB2BEF92B300C946D5 /* Build configuration list for PBXNativeTarget "TimerActivityWidgetExtension" */; + buildPhases = ( + 74BF32C32BEF92B100C946D5 /* Sources */, + 74BF32C42BEF92B100C946D5 /* Frameworks */, + 74BF32C52BEF92B100C946D5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TimerActivityWidgetExtension; + productName = TimerActivityWidgetExtension; + productReference = 74BF32C72BEF92B100C946D5 /* TimerActivityWidgetExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; C0EA6C232BB85A9100995C49 /* LiveRecipes */ = { isa = PBXNativeTarget; buildConfigurationList = C0EA6C4D2BB85A9200995C49 /* Build configuration list for PBXNativeTarget "LiveRecipes" */; @@ -585,10 +693,12 @@ C0EA6C202BB85A9100995C49 /* Sources */, C0EA6C212BB85A9100995C49 /* Frameworks */, C0EA6C222BB85A9100995C49 /* Resources */, + 74BF32DE2BEF92B300C946D5 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 74BF32D92BEF92B300C946D5 /* PBXTargetDependency */, ); name = LiveRecipes; packageProductDependencies = ( @@ -642,9 +752,12 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1520; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1520; TargetAttributes = { + 74BF32C62BEF92B100C946D5 = { + CreatedOnToolsVersion = 15.3; + }; C0EA6C232BB85A9100995C49 = { CreatedOnToolsVersion = 15.2; LastSwiftMigration = 1520; @@ -679,11 +792,20 @@ C0EA6C232BB85A9100995C49 /* LiveRecipes */, C0EA6C382BB85A9200995C49 /* LiveRecipesTests */, C0EA6C422BB85A9200995C49 /* LiveRecipesUITests */, + 74BF32C62BEF92B100C946D5 /* TimerActivityWidgetExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 74BF32C52BEF92B100C946D5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74BF32D62BEF92B300C946D5 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C0EA6C222BB85A9100995C49 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -711,6 +833,20 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 74BF32C32BEF92B100C946D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74BF32E72BF167DA00C946D5 /* LiveActivityAttributes.swift in Sources */, + 74BF32D22BEF92B100C946D5 /* TimerActivityWidget.swift in Sources */, + 74BF32D42BEF92B100C946D5 /* AppIntent.swift in Sources */, + 74BF32EA2BF1687A00C946D5 /* CustomTimerViewStyle.swift in Sources */, + 74BF32EC2BF16B2600C946D5 /* String+Localized.swift in Sources */, + 74BF32CE2BEF92B100C946D5 /* TimerActivityWidgetBundle.swift in Sources */, + 74BF32D02BEF92B100C946D5 /* TimerActivityWidgetLiveActivity.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C0EA6C202BB85A9100995C49 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -720,6 +856,7 @@ C0EA6CBB2BB85AED00995C49 /* ListModel.swift in Sources */, D0596F9B2BC806A500A1BF50 /* ListRecipeEntity+CoreDataClass.swift in Sources */, D0596F9C2BC806A500A1BF50 /* ListRecipeEntity+CoreDataProperties.swift in Sources */, + 74BF32E02BEF933C00C946D5 /* LiveActivityAttributes.swift in Sources */, 74D1B85D2BD9446400F317A9 /* DurationTextWithBlur.swift in Sources */, D0596F9D2BC806A500A1BF50 /* ListRecipeItemEntity+CoreDataClass.swift in Sources */, D0596F9E2BC806A500A1BF50 /* ListRecipeItemEntity+CoreDataProperties.swift in Sources */, @@ -735,6 +872,7 @@ C0EA6CA62BB85AED00995C49 /* CookingView.swift in Sources */, 745DA4D32BB862BF00BE4C5D /* OneDishModel.swift in Sources */, C0EA6C9C2BB85AED00995C49 /* ApplicationViewBuilder.swift in Sources */, + 74BF32E92BF1687A00C946D5 /* CustomTimerViewStyle.swift in Sources */, C0EA6CAD2BB85AED00995C49 /* FavoritesAssembly.swift in Sources */, D0596FAC2BC8633F00A1BF50 /* SettingsModel.swift in Sources */, C0EA6CA72BB85AED00995C49 /* CookingViewModel.swift in Sources */, @@ -827,6 +965,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 74BF32D92BEF92B300C946D5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 74BF32C62BEF92B100C946D5 /* TimerActivityWidgetExtension */; + targetProxy = 74BF32D82BEF92B300C946D5 /* PBXContainerItemProxy */; + }; C0EA6C3B2BB85A9200995C49 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C0EA6C232BB85A9100995C49 /* LiveRecipes */; @@ -840,6 +983,62 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 74BF32DC2BEF92B300C946D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TimerActivityWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TimerActivityWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.LiveRecipes.TimerActivityWidget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 74BF32DD2BEF92B300C946D5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TimerActivityWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TimerActivityWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ru.LiveRecipes.TimerActivityWidget; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; C0EA6C4B2BB85A9200995C49 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -962,6 +1161,7 @@ C0EA6C4E2BB85A9200995C49 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -971,6 +1171,8 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveRecipes/Info.plist; + INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -995,6 +1197,7 @@ C0EA6C4F2BB85A9200995C49 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; @@ -1004,6 +1207,8 @@ DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveRecipes/Info.plist; + INFOPLIST_KEY_NSSupportsLiveActivities = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -1103,6 +1308,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 74BF32DB2BEF92B300C946D5 /* Build configuration list for PBXNativeTarget "TimerActivityWidgetExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 74BF32DC2BEF92B300C946D5 /* Debug */, + 74BF32DD2BEF92B300C946D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C0EA6C1F2BB85A9100995C49 /* Build configuration list for PBXProject "LiveRecipes" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/LiveRecipes/Assets.xcassets/AppIcon.appiconset/Contents.json b/LiveRecipes/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..a618b3b 100644 --- a/LiveRecipes/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/LiveRecipes/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "LRW.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/LiveRecipes/Assets.xcassets/AppIcon.appiconset/LRW.png b/LiveRecipes/Assets.xcassets/AppIcon.appiconset/LRW.png new file mode 100644 index 0000000..be6eaca Binary files /dev/null and b/LiveRecipes/Assets.xcassets/AppIcon.appiconset/LRW.png differ diff --git a/LiveRecipes/Assets.xcassets/ImageEN.imageset/Contents.json b/LiveRecipes/Assets.xcassets/ImageEN.imageset/Contents.json new file mode 100644 index 0000000..ce7ce41 --- /dev/null +++ b/LiveRecipes/Assets.xcassets/ImageEN.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "frame3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LiveRecipes/Assets.xcassets/ImageEN.imageset/frame3.png b/LiveRecipes/Assets.xcassets/ImageEN.imageset/frame3.png new file mode 100644 index 0000000..829c9c6 Binary files /dev/null and b/LiveRecipes/Assets.xcassets/ImageEN.imageset/frame3.png differ diff --git a/LiveRecipes/Assets.xcassets/ImageRU.imageset/Contents.json b/LiveRecipes/Assets.xcassets/ImageRU.imageset/Contents.json new file mode 100644 index 0000000..c534056 --- /dev/null +++ b/LiveRecipes/Assets.xcassets/ImageRU.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "frame2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LiveRecipes/Assets.xcassets/ImageRU.imageset/frame2.png b/LiveRecipes/Assets.xcassets/ImageRU.imageset/frame2.png new file mode 100644 index 0000000..8bd45b8 Binary files /dev/null and b/LiveRecipes/Assets.xcassets/ImageRU.imageset/frame2.png differ diff --git a/LiveRecipes/Info.plist b/LiveRecipes/Info.plist new file mode 100644 index 0000000..666cc76 --- /dev/null +++ b/LiveRecipes/Info.plist @@ -0,0 +1,8 @@ + + + + + UIBackgroundModes + + + diff --git a/LiveRecipes/Localizable.xcstrings b/LiveRecipes/Localizable.xcstrings index 5802873..76ee7ef 100644 --- a/LiveRecipes/Localizable.xcstrings +++ b/LiveRecipes/Localizable.xcstrings @@ -3,9 +3,24 @@ "strings" : { "" : { + }, + " %lld" : { + + }, + " дн " : { + + }, + " мин " : { + + }, + " ч " : { + }, "%@" : { + }, + "%lld" : { + }, "%lldh:%lldm:%llds" : { "localizations" : { @@ -55,6 +70,108 @@ } } }, + "cookingPrepare.days" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " d. " + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : " д. " + } + } + } + }, + "cookingPrepare.dontForget" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Don't forget before cooking" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Не забудьте перед готовкой" + } + } + } + }, + "cookingPrepare.Go" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Let's go!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Поехали!" + } + } + } + }, + "cookingPrepare.hours" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " h. " + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : " ч. " + } + } + } + }, + "cookingPrepare.minutes" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " min. " + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : " мин. " + } + } + } + }, + "cookingPrepare.timeNeeded" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Time needed" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Понадобиться времени" + } + } + } + }, "cooktotime.error.message" : { "extractionState" : "manual", "localizations" : { @@ -462,6 +579,9 @@ } } } + }, + "Delete" : { + }, "filters" : { "extractionState" : "manual", @@ -650,6 +770,26 @@ } } }, + "Image" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "ImageEN" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "ImageRU" + } + } + } + }, + "Key" : { + "extractionState" : "manual" + }, "keywords.error.message" : { "extractionState" : "manual", "localizations" : { @@ -752,413 +892,994 @@ } } }, - "oneDish.toCooking" : { + "oneDish.calories" : { "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "To cooking" + "value" : "Calories" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "К приготовлению" + "value" : "Калории" } } } }, - "recents.title" : { - "extractionState" : "manual", + "oneDish.carbohydrates" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Recents" + "value" : "Carbs" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Недавние" + "state" : "translated", + "value" : "Углеводы" } } } }, - "recents.torecipes.button" : { - "extractionState" : "manual", + "oneDish.compostion" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "To recipes" + "value" : "Composition:" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "К рецептам" + "state" : "translated", + "value" : "Состав:" } } } }, - "recents.zero.message" : { - "extractionState" : "manual", + "oneDish.description" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "There's nothing here yet" + "value" : "Description:" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Тут пока ничего нет" + "state" : "translated", + "value" : "Описание:" } } } }, - "recipes.allrecipes.button" : { - "extractionState" : "manual", + "oneDish.dishNumber" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Recipes" + "value" : "Portions: 3" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Рецепты" + "state" : "translated", + "value" : "Кол-во порций: 3" } } } }, - "recipes.allrecipes.error.message" : { - "extractionState" : "manual", + "oneDish.fats" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Data upload error" + "value" : "Fats" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Ошибка загрузки данных" + "state" : "translated", + "value" : "Жиры" } } } }, - "recipes.card.time" : { - "extractionState" : "manual", + "oneDish.kcal" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : " min" + "value" : "Kcal" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : " мин" + "state" : "translated", + "value" : "Ккал" } } } }, - "recipes.cooktotime.breakfast" : { - "extractionState" : "manual", + "oneDish.nutritionalValue" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Breakfast" + "value" : "Nutritional Values" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Завтрак" + "state" : "translated", + "value" : "Пищевая ценность:" } } } }, - "recipes.cooktotime.dinner" : { - "extractionState" : "manual", + "oneDish.nutritionalValue100gr" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Dinner" + "value" : "Nutritional Values for 100g" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Ужин" + "state" : "translated", + "value" : "Пищевая ценность на 100гр" } } } }, - "recipes.cooktotime.label" : { - "extractionState" : "manual", + "oneDish.proteins" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Cook to time" + "value" : "Proteins" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Приготовьте ко времени" + "state" : "translated", + "value" : "Белки" } } } }, - "recipes.cooktotime.lunch" : { - "extractionState" : "manual", + "oneDish.timeToCook" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Lunch" + "value" : "Time needed:" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Обед" + "state" : "translated", + "value" : "Время приготовления" } } } }, - "recipes.cooktotime.snack" : { - "extractionState" : "manual", + "oneDish.toCooking" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Snack" + "value" : "To cooking" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Перекус" + "state" : "translated", + "value" : "К приготовлению" } } } }, - "recipes.keywords.button" : { + "oneStep.lastStep" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Search by keywords" + "value" : "Last step" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Найти по ключевым словам" + "state" : "translated", + "value" : "Последний шаг" } } } }, - "recipes.keywords.error.message" : { - "extractionState" : "manual", + "oneStep.nextStep" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Data upload error" + "value" : "Next step" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Ошибка загрузки данных" + "state" : "translated", + "value" : "Следующий шаг" } } } }, - "recipes.keywords.more" : { + "oneStep.step1" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "More keywords" + "value" : "Step 1" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Больше слов" + "state" : "translated", + "value" : "Шаг 1" } } } }, - "recipes.myrecipes.button" : { + "oneStep.step2" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "My recipes" + "value" : "Step 2" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Мои рецепты" + "state" : "translated", + "value" : "Шаг 2" } } } }, - "recipes.myrecipes.zero.info" : { + "oneStep.step3" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "The recipes you have created will be displayed here" + "value" : "Step 3" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Тут будут отображаться созданные Вами рецепты" + "state" : "translated", + "value" : "Шаг 3" } } } }, - "recipes.myrecipes.zero.message" : { + "oneStep.step4" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "It's empty here yet" + "value" : "Step 4" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Здесь пока пусто" + "state" : "translated", + "value" : "Шаг 4" } } } }, - "recipes.recents.button" : { + "oneStep.step5" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Recents" + "value" : "Step 5" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Недавние" + "state" : "translated", + "value" : "Шаг 5" } } } }, - "recipes.recents.zero.info" : { + "oneStep.step6" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Previously viewed recipes will be displayed here" + "value" : "Step 6" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Тут будут отображаться ранее просмотренные рецепты" + "state" : "translated", + "value" : "Шаг 6" } } } }, - "recipes.recents.zero.message" : { + "oneStep.step7" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "It's empty here yet" + "value" : "Step 7" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Здесь пока пусто" + "state" : "translated", + "value" : "Шаг 7" } } } }, - "Select a segment" : { - - }, - "settings" : { + "oneStep.step8" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Step 8" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Шаг 8" } } } }, - "settings.clearFavourites" : { + "oneStep.step9" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Clear favourites" + "value" : "Step 9" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить избранное" + "value" : "Шаг 9" } } } }, - "settings.clearList" : { + "oneStep.step10" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Clear the shopping list" + "value" : "Step 10" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить список покупок" + "value" : "Шаг 10" + } + } + } + }, + "oneStep.step11" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 11" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 11" + } + } + } + }, + "oneStep.step12" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 12" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 12" + } + } + } + }, + "oneStep.step13" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 13" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 13" + } + } + } + }, + "oneStep.step14" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 14" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 14" + } + } + } + }, + "oneStep.step15" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 15" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 15" + } + } + } + }, + "oneStep.step16" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 16" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 16" + } + } + } + }, + "oneStep.step17" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 17" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 17" + } + } + } + }, + "oneStep.step18" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 18" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 18" + } + } + } + }, + "oneStep.step19" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 19" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 19" + } + } + } + }, + "oneStep.step20" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 20" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 20" + } + } + } + }, + "oneStep.toMainPage" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Main page" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "На главную" + } + } + } + }, + "recents.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recents" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Недавние" + } + } + } + }, + "recents.torecipes.button" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "To recipes" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "К рецептам" + } + } + } + }, + "recents.zero.message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There's nothing here yet" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Тут пока ничего нет" + } + } + } + }, + "recipes.allrecipes.button" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recipes" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Рецепты" + } + } + } + }, + "recipes.allrecipes.error.message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Data upload error" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Ошибка загрузки данных" + } + } + } + }, + "recipes.card.time" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " min" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : " мин" + } + } + } + }, + "recipes.cooktotime.breakfast" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Breakfast" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Завтрак" + } + } + } + }, + "recipes.cooktotime.dinner" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dinner" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Ужин" + } + } + } + }, + "recipes.cooktotime.label" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cook to time" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Приготовьте ко времени" + } + } + } + }, + "recipes.cooktotime.lunch" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lunch" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Обед" + } + } + } + }, + "recipes.cooktotime.snack" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Snack" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Перекус" + } + } + } + }, + "recipes.keywords.button" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search by keywords" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Найти по ключевым словам" + } + } + } + }, + "recipes.keywords.error.message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Data upload error" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Ошибка загрузки данных" + } + } + } + }, + "recipes.keywords.more" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "More keywords" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Больше слов" + } + } + } + }, + "recipes.myrecipes.button" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "My recipes" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Мои рецепты" + } + } + } + }, + "recipes.myrecipes.zero.info" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The recipes you have created will be displayed here" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Тут будут отображаться созданные Вами рецепты" + } + } + } + }, + "recipes.myrecipes.zero.message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "It's empty here yet" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Здесь пока пусто" + } + } + } + }, + "recipes.recents.button" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recents" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Недавние" + } + } + } + }, + "recipes.recents.zero.info" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Previously viewed recipes will be displayed here" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Тут будут отображаться ранее просмотренные рецепты" + } + } + } + }, + "recipes.recents.zero.message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "It's empty here yet" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Здесь пока пусто" + } + } + } + }, + "Select a segment" : { + + }, + "Set time for Timer" : { + + }, + "settings" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настройки" + } + } + } + }, + "settings.clearFavourites" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clear favourites" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очистить избранное" + } + } + } + }, + "settings.clearList" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clear the shopping list" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очистить список покупок" } } } @@ -1282,16 +2003,62 @@ } } }, - "Белки" : { - + "timer" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timer" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Таймер" + } + } + } }, - "Время приготовления:" : { - + "timer.over" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Timer is over" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Таймер завершен" + } + } + } + }, + "timer.step.completed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " completed" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : " завершен" + } + } + } }, "Выберете рецепт и начните готовить!" : { }, "г" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1307,77 +2074,40 @@ } } }, + "Гарнир" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Garnish" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Гарнир" + } + } + } + }, "Готовка" : { }, - "Жиры" : { + "Готово!" : { }, "К рецептам" : { - }, - "Калории" : { - - }, - "Ккал" : { - - }, - "Кол-во порций: 5" : { - - }, - "На главную" : { - - }, - "Не забудьте перед готовкой" : { - - }, - "Описание:" : { - }, "Ошибка загрузки данных" : { - }, - "Пищевая ценность на 100г" : { - - }, - "Пищевая ценность:" : { - - }, - "Поехали!" : { - - }, - "Понадобиться времени" : { - - }, - "Последний шаг" : { - - }, - "Следующий шаг" : { - }, "Создать" : { - }, - "Состав:" : { - - }, - "Таймер" : { - }, "Тут пока ничего нет" : { - }, - "Углеводы" : { - - }, - "Цезарь" : { - - }, - "Цезарь с креветками" : { - - }, - "Шаг %lld" : { - } }, "version" : "1.0" diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift index 418fc98..b83658b 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift @@ -6,6 +6,9 @@ // import SwiftUI +import ActivityKit +import Foundation +import UserNotifications struct BlinkingText: View { @State private var isVisible = true @@ -33,13 +36,20 @@ struct BlinkingText: View { } struct TimerView: View { + @State private var currentActivity: Activity? + @State var isTimerRunning = false @State private var progress: Int = 0 @State private var isPaused = false var totalTime: Int + @State var timeForProgress: Int - @Binding var disconnectText: Bool + @State var activityStarted: Bool = false + + var step: String? + var stepsCount: Int? + var dishName: String? let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() @@ -50,7 +60,56 @@ struct TimerView: View { return (hours, minutes, seconds) } - + + func notify() -> Void { + let content = UNMutableNotificationContent() + content.title = "LiveRecipes" + if let step = step { + content.body = step + "timer.step.completed".localized + } + else { + content.body = "timer.over".localized + } + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + let req = UNNotificationRequest(identifier: "MSG", content: content, trigger: trigger) + + UNUserNotificationCenter.current().add(req, withCompletionHandler: nil) + } + + func stopActivity() { + Task { + await currentActivity?.end(nil, dismissalPolicy: .after(Date().addingTimeInterval(10))) + activityStarted = false + } + } + + func updateActivity() { + Task { + let state = TimerAttributes.TimeState(progress: progress, totalTime: totalTime, timeRemaining: timeForProgress, currentStep: step ?? "", stepsCount: stepsCount ?? 0, interval: nil) + //await currentActivity?.update(using: state) + await currentActivity?.update(ActivityContent(state: state, staleDate: nil)) + //await currentActivity?.update(using: state) + } + } + + func startActivity() { + activityStarted = true + let startTime = TimeInterval(totalTime) + let endTime = TimeInterval(0) + let interval = ClosedRange(uncheckedBounds: (lower: Date().addingTimeInterval(endTime), upper: Date().addingTimeInterval(startTime))) + + let attributes = TimerAttributes(dishName: dishName ?? "") + let state = TimerAttributes.TimeState(progress: 0, totalTime: totalTime, timeRemaining: timeForProgress, currentStep: step ?? "", stepsCount: stepsCount ?? 0, interval: interval) + let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 0.0) + + do { + currentActivity = try Activity.request(attributes: attributes, content: content) + } + catch { + print(error.localizedDescription) + } + } + var body: some View { let (hours, minutes, seconds) = minutesAndSeconds @@ -60,7 +119,7 @@ struct TimerView: View { isTimerRunning = false progress = 0 timeForProgress = totalTime - disconnectText = false + stopActivity() }) { Image(systemName: "arrow.counterclockwise") .imageScale(.large) @@ -68,14 +127,21 @@ struct TimerView: View { Spacer() - Text("Таймер") + Text("timer".localized) + Image(systemName: "timer") Spacer() Button(action: { isTimerRunning.toggle() - + if activityStarted { + // updateActivity() + } + else { + startActivity() + //progress = timeForProgress + } }) { Image(systemName: isTimerRunning ? "pause.fill" : "play.fill") .imageScale(.large) @@ -84,10 +150,13 @@ struct TimerView: View { }.padding(.init(top: 8, leading: 8, bottom: 0, trailing: 8)) ZStack { - ProgressView(value: Double(progress), total: Double(totalTime)) + ProgressView(value: Double(timeForProgress), total: Double(totalTime)) .progressViewStyle(CustomProgressViewStyle(progress: $progress)) if timeForProgress == 0 { - BlinkingText(text: "Готово", disconnectTimer: $disconnectText) + Text("Готово!") + .foregroundColor(.orange) + .font(.system(size: 19, weight: .semibold)) + .padding(.top, 2) } else { Text(hours == 0 ? "\(minutes)m:\(seconds)s" : "\(hours)h:\(minutes)m:\(seconds)s") @@ -100,11 +169,14 @@ struct TimerView: View { if progress < totalTime { progress += 1 timeForProgress -= 1 - + //updateActivity() } + if progress == totalTime { - disconnectText = false isTimerRunning = false + //updateActivity() + stopActivity() + notify() } } } @@ -113,7 +185,7 @@ struct TimerView: View { .padding(.init(top: 0, leading: 8, bottom: 16, trailing: 8)) }.background(Color(UIColor.systemGray6)) .cornerRadius(10) - .padding() + .frame(width: UIScreen.main.bounds.width - 20) } } @@ -129,7 +201,7 @@ struct CustomProgressViewStyle: ProgressViewStyle { .overlay(alignment: .leading){ RoundedRectangle(cornerRadius: 15) .foregroundColor(.orange) - .frame(width: configuration.fractionCompleted.map { $0 * geometry.size.width }, height: 30) + .frame(width: configuration.fractionCompleted.map { $0 * geometry.size.width }, height: configuration.fractionCompleted ?? 0 < 0.025 ? 15 : 30) .animation(.linear, value: progress) } } diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/DurationTextWithBlur.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/DurationTextWithBlur.swift index e3ee08f..0af5673 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/DurationTextWithBlur.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/DurationTextWithBlur.swift @@ -13,7 +13,7 @@ struct DurationTextWithBlur: View { @State private var opacity: Double = 0 var body: some View { - Text("Понадобиться времени") + Text("cookingPrepare.timeNeeded".localized) .font(.title2) .foregroundColor(.black) .blur(radius: blurRadius) @@ -29,6 +29,7 @@ struct DurationTextWithBlur: View { self.blurRadius = 0 } } + Text(text) .font(.largeTitle) .foregroundColor(.orange) diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/IngredientRunningTextView.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/IngredientRunningTextView.swift index 0513fb7..128229b 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/IngredientRunningTextView.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/IngredientRunningTextView.swift @@ -6,7 +6,6 @@ // import SwiftUI - struct IngredientRunningTextView: View { @State private var textOffset: CGSize var duration: Double @@ -40,7 +39,55 @@ struct IngredientRunningTextView: View { .onAppear { withAnimation(.bouncy(duration: duration)) { textOffset = CGSize(width: finishWidth, height: 0) + let _ = print(UIScreen.main.bounds.width) } } } } + +struct IngredientRunningTextView2: View { + + @State var computedFinishX: CGFloat + var duration: Double + var text: String + var startX: CGFloat + + init(duration: Double, text: String, startX: CGFloat) { + self.computedFinishX = startX + self.duration = duration + self.text = text + self.startX = startX + + } + + func getWidth(for text: String) -> CGFloat { + let size = (text as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18)]) + return size.width + } + + var body: some View { + VStack { + Spacer() + Text(text) + .frame(width: getWidth(for: text) + 25, height: 50) + .background(.white) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .font(.system(size: 18, weight: .regular)) + .foregroundColor(.black) + .offset(x: computedFinishX, y: 0) + Spacer() + } + .onAppear { + withAnimation(.bouncy(duration: duration)) { + if startX < 0 { + computedFinishX = (UIScreen.main.bounds.width - getWidth(for: text) - 25) / 2 - 8 + + } + else { + computedFinishX = -((UIScreen.main.bounds.width - getWidth(for: text) - 25) / 2 - 8) + } + } + } + } + } + diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift index edcd237..243e1c4 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift @@ -10,26 +10,25 @@ import Swinject struct StartCookingButton: View { @State private var buttonOffset = CGSize(width: 0, height: 300) - @State private var tabSelected = Tabs.recipes + var transferData: RecipeDTO var body: some View { VStack { Spacer() Button(action: { - + }) { NavigationLink(destination:{ - Assembler.sharedAssembly - .resolver - .resolve(CookingView.self, argument: $tabSelected)}) { - Text("Поехали!") - .font(.title2) - .foregroundColor(.white) - .padding() - .background(Color.orange) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .offset(buttonOffset) - } + OneStepView(stepNumber: 1, steps: transferData.steps, dishName: transferData.name, dishType: getTranslation(transferData.tag)) + }) { + Text("cookingPrepare.Go".localized) + .font(.title2) + .foregroundColor(.white) + .padding() + .background(Color.orange) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .offset(buttonOffset) + } } .onAppear { withAnimation(.easeOut(duration: 3.5)) { @@ -40,3 +39,36 @@ struct StartCookingButton: View { } } } + +//struct StartCookingButton: View { +// @State private var buttonOffset = CGSize(width: 0, height: 300) +// @State private var tabSelected = Tabs.recipes +// +// var body: some View { +// VStack { +// Spacer() +// Button(action: { +// +// }) { +// NavigationLink(destination:{ +// Assembler.sharedAssembly +// .resolver +// .resolve(CookingView.self, argument: $tabSelected)}) { +// Text("Поехали!") +// .font(.title2) +// .foregroundColor(.white) +// .padding() +// .background(Color.orange) +// .clipShape(RoundedRectangle(cornerRadius: 10)) +// .offset(buttonOffset) +// } +// } +// .onAppear { +// withAnimation(.easeOut(duration: 3.5)) { +// buttonOffset = CGSize.zero +// } +// } +// Spacer() +// } +// } +//} diff --git a/LiveRecipes/Modules/Cooking/Components/CustomTimerViewStyle.swift b/LiveRecipes/Modules/Cooking/Components/CustomTimerViewStyle.swift new file mode 100644 index 0000000..da75004 --- /dev/null +++ b/LiveRecipes/Modules/Cooking/Components/CustomTimerViewStyle.swift @@ -0,0 +1,28 @@ +// +// CustomTimerViewStyle.swift +// LiveRecipes +// +// Created by Сергей Мирошниченко on 13.05.2024. +// + +import SwiftUI + +struct CustomTimerViewStyle: ProgressViewStyle { + var progress: Int + + func makeBody(configuration: Configuration) -> some View { + GeometryReader { geometry in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 15) + .foregroundColor(Color(UIColor.white).opacity(1)) + .frame(width: geometry.size.width, height: 12) + .overlay(alignment: .leading){ + RoundedRectangle(cornerRadius: 15) + .foregroundColor(.orange) + .frame(width: configuration.fractionCompleted.map { $0 * (geometry.size.width)}, height: configuration.fractionCompleted! < 0.025 ? 6 : 12) + .animation(.linear, value: progress) + } + } + } + } +} diff --git a/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift b/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift new file mode 100644 index 0000000..17bc5b6 --- /dev/null +++ b/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift @@ -0,0 +1,25 @@ +// +// LiveActivityAttributes.swift +// LiveRecipes +// +// Created by Сергей Мирошниченко on 11.05.2024. +// + +import Foundation +import ActivityKit +import SwiftUI + +public struct TimerAttributes: ActivityAttributes { + public typealias TimeState = ContentState + + public struct ContentState: Codable, Hashable { + var progress: Int + var totalTime: Int + var timeRemaining: Int + var currentStep: String + var stepsCount: Int + var interval: ClosedRange? + } + + var dishName: String +} diff --git a/LiveRecipes/Modules/Cooking/NoStepsView.swift b/LiveRecipes/Modules/Cooking/Components/NoStepsView.swift similarity index 87% rename from LiveRecipes/Modules/Cooking/NoStepsView.swift rename to LiveRecipes/Modules/Cooking/Components/NoStepsView.swift index fb02070..5771315 100644 --- a/LiveRecipes/Modules/Cooking/NoStepsView.swift +++ b/LiveRecipes/Modules/Cooking/Components/NoStepsView.swift @@ -10,6 +10,10 @@ import SwiftUI struct NoStepsView: View { @Binding var tabSelected: Tabs +// init(tabSelected: Binding?) { +// self._tabSelected = tabSelected ?? Binding.constant(nil) +// } + var body: some View { NavigationView { VStack { @@ -27,7 +31,7 @@ struct NoStepsView: View { .font(.headline) .foregroundStyle(.orange) .gesture(TapGesture().onEnded({ - withAnimation(.linear(duration: 2)) { + withAnimation { tabSelected = Tabs.recipes } })) diff --git a/LiveRecipes/Modules/Cooking/Components/OneStepView.swift b/LiveRecipes/Modules/Cooking/Components/OneStepView.swift new file mode 100644 index 0000000..a243d1e --- /dev/null +++ b/LiveRecipes/Modules/Cooking/Components/OneStepView.swift @@ -0,0 +1,167 @@ +// +// OneStepView.swift +// LiveRecipes +// +// Created by Сергей Мирошниченко on 12.04.2024. +// + +import SwiftUI +import Swinject +import ActivityKit + + +struct OneStepView: View { + //@StateObject var viewModel: CookingViewModel + @State var isTimerOnView = false + @State var isLanded = false + @State var isDatePicker = false + @State var blurIsActive = false + @State var firstAppearence = true + var stepNumber: Int + var steps: [[String]] + var dishName: String + var dishType: String + + var body: some View { + ZStack(alignment: .center) { + + ScrollView { + HStack { + VStack(alignment: .leading) { + Text(dishName) + .font(.system(size: 20, weight: .medium)) + Text(dishType) + .font(.system(size: 16, weight: .light)) + } + Spacer() + } + .padding(.init(top: 0, leading: 16, bottom: 0, trailing: 10)) + + VStack { + VStack { + if let data = Data(base64Encoded: steps[stepNumber - 1][1]), let image = UIImage(data: data) { + Image(uiImage: image) + .resizable() + .frame(width: UIScreen.main.bounds.width - 20, height: 260) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + } + Text(steps[stepNumber - 1][0]) + .lineSpacing(8) + .padding(.init(top: 8, leading: 8, bottom: 8, trailing: 8)) + }.background(Color(UIColor.secondarySystemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + .padding() + + if steps[stepNumber - 1][2] != "0" { + TimerView(totalTime: Int(steps[stepNumber - 1][2]) ?? 0, timeForProgress: Int(steps[stepNumber - 1][2]) ?? 0, step: "oneStep.step\(stepNumber)".localized, stepsCount: steps.count, dishName: dishName) + .onAppear { + isTimerOnView = true + } + } + else { + TimerView(totalTime: 20, timeForProgress: 20, step: "oneStep.step\(stepNumber)".localized, stepsCount: steps.count, dishName: dishName) + .offset(y: isLanded ? 0 : -1000) + .contextMenu(menuItems: { + Button("Delete", systemImage: "trash") { + withAnimation(.spring()) { + isLanded = false + isTimerOnView = false + } + } + Button("Set time for Timer", systemImage: "gear") { + isDatePicker = true + } + }) + } + if stepNumber + 1 <= steps.count { + Button(action: { + + }) { + NavigationLink(destination: OneStepView(stepNumber: stepNumber + 1, steps: steps, dishName: dishName, dishType: dishType)) { + HStack { + Spacer() + Text("oneStep.nextStep".localized) + .foregroundStyle(.white) + .fontWeight(.semibold) + Image(systemName: "chevron.right") + .foregroundStyle(.white) + .frame(height: 35) + Spacer() + } + + } + + }.buttonStyle(.borderedProminent) + .tint(.orange) + .padding(.init(top: 0, leading: 0, bottom: 40, trailing: 0)) + .frame(width: UIScreen.main.bounds.width - 20) + .offset(y: isTimerOnView ? 10 : -100) + + } + else { + Button(action: { + + }) { + NavigationLink(destination:{ + Assembler.sharedAssembly + .resolver + .resolve(RecipesView.self)}) { + HStack { + Spacer() + Text("oneStep.toMainPage".localized) + .foregroundStyle(.white) + .fontWeight(.semibold) + + Image(systemName: "fork.knife") + .foregroundStyle(.white) + .frame(height: 35) + Spacer() + } + } + + } + .buttonStyle(.borderedProminent) + .tint(.black) + .padding(.bottom, 20) + .frame(width: UIScreen.main.bounds.width - 20) + .offset(y: isTimerOnView ? 10 : -100) + } + } + } + .blur(radius: blurIsActive ? 20 : 0) + .onAppear { + + if stepNumber == 1 && firstAppearence { + blurIsActive = true + } + firstAppearence = false + } + if blurIsActive { + Image("Image".localized) + } + } + .onTapGesture { + blurIsActive = false + } + .navigationTitle(stepNumber == steps.count ? "oneStep.lastStep".localized :"oneStep.step\(stepNumber)".localized) + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(stepNumber == 1 ? true : false) + .toolbar(.visible, for: .tabBar) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button (action: { + withAnimation(.spring()) { + isLanded = true + isTimerOnView = true + } + }) { + Image(systemName: "timer") + .imageScale(.large) + .foregroundStyle(.orange) + }.opacity(isTimerOnView ? 0.0 : 1.0) + } + } + + } +} + diff --git a/LiveRecipes/Modules/Cooking/Components/PrepareForCookingView.swift b/LiveRecipes/Modules/Cooking/Components/PrepareForCookingView.swift new file mode 100644 index 0000000..3af3674 --- /dev/null +++ b/LiveRecipes/Modules/Cooking/Components/PrepareForCookingView.swift @@ -0,0 +1,82 @@ +// +// PrepareForCookingView.swift +// LiveRecipes +// +// Created by Сергей Мирошниченко on 23.04.2024. +// + +import SwiftUI +import Swinject + +struct PrepareForCookingView: View { + @State private var animatedTextIndex = 0 + @State private var durationText = "" + @Environment(\.presentationMode) var presentationMode + + let recipe: RecipeDTO + + var body: some View { + GeometryReader { geometry in + VStack { + Spacer() + ZStack(alignment: .bottom) { + ScrollView { + ForEach(0.. Void) +} -protocol OneDishViewModelProtocol {} +protocol OneDishViewModelProtocol { + func findRecipe() -> Void +} diff --git a/LiveRecipes/Modules/OneDish/OneDishView.swift b/LiveRecipes/Modules/OneDish/OneDishView.swift index 5b0c008..30b291a 100644 --- a/LiveRecipes/Modules/OneDish/OneDishView.swift +++ b/LiveRecipes/Modules/OneDish/OneDishView.swift @@ -8,25 +8,31 @@ import SwiftUI import Swinject + struct OneDishView: View { @StateObject var viewState: OneDishViewModel @State private var isScrollDown = true + @State var crs: CGFloat = 0 @State var minYwritten = false @State var globalMinY: CGFloat = 0 - var openedFromRecipesView: Bool = true + var openedFromRecipesView: Bool = true var body: some View { - ZStack(alignment: .bottom) { + if viewState.foundRecipe.ingredients.isEmpty { + ProgressView() + } + else { + ZStack(alignment: .bottom) { ScrollView { VStack { VStack { if let data = Data(base64Encoded: viewState.foundRecipe.photo), let image = UIImage(data: data) { - Image(uiImage: image) - .resizable() - .frame(width: 370, height: 260) - + Image(uiImage: image) + .resizable() + .frame(width: UIScreen.main.bounds.width - 20, height: 280) + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) } else { Image("caesar") @@ -34,22 +40,38 @@ struct OneDishView: View { .frame(width: 370, height: 260) } - Text(viewState.foundRecipe.name) - .font(.system(size: 22)) - .fontWeight(.medium) - .padding(.bottom, 5) + Text(viewState.foundRecipe.name) + .font(.system(size: 20)) + .fontWeight(.semibold) + .padding(.bottom, 5) + HStack { Image(systemName: "clock") .foregroundStyle(.orange) - Text("Время приготовления:") - Text(viewState.foundRecipe.duration) + HStack(spacing: 0){ + Text("oneDish.timeToCook".localized) + if viewState.foundRecipe.decomposeDuration().0 != 0 { + Text(" \(viewState.foundRecipe.decomposeDuration().0)") + Text(" дн ") + Text("\(viewState.foundRecipe.decomposeDuration().1)") + Text(" ч ") + Text("\(viewState.foundRecipe.decomposeDuration().2)") + Text(" мин ") + } + else { + Text(" \(viewState.foundRecipe.decomposeDuration().1)") + Text(" ч ") + Text("\(viewState.foundRecipe.decomposeDuration().2)") + Text(" мин ") + } + } Spacer() } .padding(.leading, 8) HStack { Image(systemName: "fork.knife.circle") .foregroundStyle(.orange) - Text("Пищевая ценность:") + Text("oneDish.nutritionalValue".localized) Spacer() }.padding(.leading, 8) @@ -57,12 +79,12 @@ struct OneDishView: View { HStack { VStack { - Text("Калории") + Text("oneDish.calories".localized) .foregroundStyle(Color(.systemGray)) .padding(.top, 5) HStack(spacing: 0) { Text("\(viewState.foundRecipe.bzy.calories)") - Text("Ккал") + Text("oneDish.kcal".localized) } .font(.title) .fontWeight(.bold) @@ -71,39 +93,39 @@ struct OneDishView: View { }.padding(.trailing, 10) Divider().frame(height: 60) VStack { - Text("Белки") + Text("oneDish.proteins".localized) .foregroundStyle(Color(.systemGray)) .padding(.top, 5) HStack(spacing: 0) { Text("\(viewState.foundRecipe.bzy.protein)") - Text("г") - + Text("г".localized) + } .font(.title) - .fontWeight(.bold) - .foregroundStyle(Color(.systemGray)) - .multilineTextAlignment(.center) + .fontWeight(.bold) + .foregroundStyle(Color(.systemGray)) + .multilineTextAlignment(.center) } Divider().frame(height: 60) VStack { - Text("Жиры") + Text("oneDish.fats".localized) .foregroundStyle(Color(.systemGray)) .padding(.top, 5) HStack(spacing: 0) { Text(viewState.foundRecipe.bzy.fats) - Text("г") + Text("г".localized) }.font(.title) .fontWeight(.bold) .foregroundStyle(Color(.systemGray)) } Divider().frame(height: 60) VStack { - Text("Углеводы") + Text("oneDish.carbohydrates".localized) .padding(.top, 5) .foregroundStyle(Color(.systemGray)) HStack(spacing: 0) { Text(viewState.foundRecipe.bzy.carbohydrates) - Text("г") + Text("г".localized) }.font(.title) .fontWeight(.bold) .foregroundStyle(Color(.systemGray)) @@ -113,7 +135,7 @@ struct OneDishView: View { Divider() - Text("Пищевая ценность на 100г") + Text("oneDish.nutritionalValue100gr".localized) .font(.system(size: 13)) .padding(.bottom, 20) .foregroundStyle(Color(.systemGray)) @@ -121,70 +143,43 @@ struct OneDishView: View { HStack { Image(systemName: "carrot") .foregroundStyle(.orange) - Text("Состав:") + Text("oneDish.compostion".localized) Spacer() - Text("Кол-во порций: 5") + Text("oneDish.dishNumber".localized) .fontWeight(.light) .padding(.trailing, 8) }.padding(.leading, 8) .padding(.bottom, 10) - ForEach(viewState.foundRecipe.ingredients, id: \.self) { ingredient in + ForEach(Array(viewState.foundRecipe.ingredients.enumerated()), id: \.offset) { index, ingredient in HStack { Image(systemName: "smallcircle.filled.circle") + .foregroundStyle(.black) Text(ingredient) Spacer() + }.padding(.leading, 10) .padding(.bottom, 8) } -// HStack { -// Image(systemName: "smallcircle.filled.circle") -// Text("Креветки 4шт") -// Spacer() -// }.padding(.leading, 10) -// HStack { -// Image(systemName: "smallcircle.filled.circle") -// Text("Салат 8 листов") -// Spacer() -// }.padding(.leading, 10) -// HStack { -// Image(systemName: "smallcircle.filled.circle") -// Text("Сухарики 9 грамм") -// Spacer() -// }.padding(.leading, 10) -// HStack { -// Image(systemName: "smallcircle.filled.circle") -// Text("Соль по вкусу") -// Spacer() -// }.padding(.leading, 10) -// HStack { -// Image(systemName: "smallcircle.filled.circle") -// Text("Перец по вкусу") -// Spacer() -// Text("Добавить продукты\n в список покупок") -// .font(.system(size: 12)) -// .foregroundStyle(.orange) -// Image(systemName: "cart") -// .foregroundStyle(.orange) -// -// -// }.padding(.init(top: 0, leading: 10, bottom: 10, trailing: 10)) }.background(Color(UIColor.secondarySystemBackground)) .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) - .padding() + .frame(width: UIScreen.main.bounds.width - 20) HStack { - Text("Описание:") - .font(.title3) + Text("oneDish.description".localized) + .font(.system(size: 24, weight: .regular)) Spacer() } - .padding(.init(top: 0, leading: 18, bottom: 5, trailing: 0)) + .padding(.init(top: 16, leading: 0, bottom: 1, trailing: 0)) + .frame(width: UIScreen.main.bounds.width - 20) + //.padding(.init(top: 0, leading: 18, bottom: 5, trailing: 0)) HStack { Text(viewState.foundRecipe.description) - .lineSpacing(4) - //Text("oneDish.caesar.description") + .lineSpacing(8) - }.padding(.init(top: 0, leading: 18, bottom: 10, trailing: 10)) + }.frame(width: UIScreen.main.bounds.width - 20) + //.padding(.top, 1) + //.padding(.init(top: 0, leading: 18, bottom: 10, trailing: 10)) } .padding() @@ -222,25 +217,30 @@ struct OneDishView: View { } } - }.navigationTitle("Цезарь") - - Button(action: { + }.navigationTitle(viewState.foundRecipe.name) - }) { - NavigationLink(destination:{ - PrepareForCookingView() + Button(action: { + + }) { + NavigationLink(destination:{ + PrepareForCookingView(recipe: viewState.foundRecipe) }) { - Image(systemName: "stove.fill") - .foregroundColor(.white) - Text("oneDish.toCooking".localized) - .frame(width: 150, height: 35) - .foregroundStyle(.white) - .fontWeight(.medium) + HStack { + Spacer() + Image(systemName: "stove.fill") + .imageScale(.large) + .foregroundColor(.white) + Text("oneDish.toCooking".localized) + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(.white) + Spacer() + }.frame(width: 200, height: 35) } - }.buttonStyle(.borderedProminent) - .tint(.orange) - .opacity(isScrollDown ? 1.0 : 0.0) - .padding() + }.buttonStyle(.borderedProminent) + .tint(.orange) + .opacity(isScrollDown ? 1.0 : 0.0) + .padding() + } // Button(action: { // diff --git a/LiveRecipes/Modules/OneDish/OneDishViewModel.swift b/LiveRecipes/Modules/OneDish/OneDishViewModel.swift index 9eb17a7..40eb723 100644 --- a/LiveRecipes/Modules/OneDish/OneDishViewModel.swift +++ b/LiveRecipes/Modules/OneDish/OneDishViewModel.swift @@ -9,19 +9,17 @@ import Foundation final class OneDishViewModel: ObservableObject, OneDishViewModelProtocol { var model: OneDishModel - var id = 1 - @Published var foundRecipe = RecipeDTO(id: 0, name: "", bzy: BZY(calories: "0", protein: "0", fats: "0", carbohydrates: "0"), duration: "", photo: "", description: "", ingredients: [], steps: [[]], tag: "") + var id = 121 + @Published var foundRecipe = RecipeDTO(id: 0, name: "", bzy: BZY(calories: "0", protein: "0", fats: "0", carbohydrates: "0"), duration: 0, photo: "", description: "", ingredients: [], steps: [[]], tag: "") init(oneDishModel: OneDishModel) { self.model = oneDishModel model.findRecipe(id: id, completion: { [weak self] result in self?.foundRecipe = result - print("smth") - print(self?.foundRecipe.name) }) } - func findRecipes() { + func findRecipe() { model.findRecipe(id: id) { [weak self] result in self?.foundRecipe = result } diff --git a/LiveRecipes/Modules/Recipes/RecipesView.swift b/LiveRecipes/Modules/Recipes/RecipesView.swift index fd619c9..8aefaa7 100644 --- a/LiveRecipes/Modules/Recipes/RecipesView.swift +++ b/LiveRecipes/Modules/Recipes/RecipesView.swift @@ -50,6 +50,7 @@ import Foundation import SwiftUI import Swinject +import UserNotifications struct RecipesView: View { @StateObject var viewModel: RecipesViewModel @@ -69,6 +70,12 @@ struct RecipesView: View { myRecipesView() } } + .onAppear { + UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) + { (_, _) in + + } + } .searchable(text: $searchText) .refreshable(action: { print("refresh") diff --git a/LiveRecipes/Services/NetworkService/Target/Recipe.swift b/LiveRecipes/Services/NetworkService/Target/Recipe.swift index 43f41af..9010212 100644 --- a/LiveRecipes/Services/NetworkService/Target/Recipe.swift +++ b/LiveRecipes/Services/NetworkService/Target/Recipe.swift @@ -17,7 +17,7 @@ enum RecipeTarget { extension RecipeTarget: TargetType { var baseURL: String { //return "https://api.api-ninjas.com/v1" - return "http://127.0.0.1:8000" + return "https://liverecipes.online" } var path: String { diff --git a/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift b/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift index b98a442..610acd2 100644 --- a/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift +++ b/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift @@ -18,14 +18,50 @@ struct RecipeDTO: Codable, Hashable { var id: Int var name: String var bzy: BZY - var duration: String + var duration: Int var photo: String var description: String var ingredients: [String] var steps: [[String]] var tag: String + + func decomposeDuration() -> (Int, Int, Int) { + let days = duration / 60 / 24 + let hours = duration / 60 + let minutes = duration % 60 + + return (days, hours, minutes) + } } +func getTranslation(_ dishType: String) -> String { + switch dishType { + case "dessert": + return "Дессерт".localized + case "drink": + return "Напиток".localized + case "snack": + return "Закуска".localized + case "garnish": + return "Гарнир".localized + case "firstDish": + return "Первое блюдо".localized + case "salad": + return "Салат".localized + case "sauce": + return "Соус".localized + case "secondDish": + return "Второе блюдо".localized + case "bakery": + return "Выпечка".localized + case "harvesting": + return "Заготовка".localized + default: + return "Блюдо".localized + } +} + + //struct RecipeDTO: Codable, Hashable { // var title: String // var ingredients: String diff --git a/TimerActivityWidget/AppIntent.swift b/TimerActivityWidget/AppIntent.swift new file mode 100644 index 0000000..8ee5815 --- /dev/null +++ b/TimerActivityWidget/AppIntent.swift @@ -0,0 +1,19 @@ +// +// AppIntent.swift +// TimerActivityWidget +// +// Created by Сергей Мирошниченко on 11.05.2024. +// + +import WidgetKit +import AppIntents + +@available(iOSApplicationExtension 17.0, *) +struct ConfigurationAppIntent: WidgetConfigurationIntent { + static var title: LocalizedStringResource = "Configuration" + static var description = IntentDescription("This is an example widget.") + + // An example configurable parameter. + @Parameter(title: "Favorite Emoji", default: "😃") + var favoriteEmoji: String +} diff --git a/TimerActivityWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/TimerActivityWidget/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/TimerActivityWidget/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TimerActivityWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/TimerActivityWidget/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/TimerActivityWidget/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TimerActivityWidget/Assets.xcassets/Contents.json b/TimerActivityWidget/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/TimerActivityWidget/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TimerActivityWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/TimerActivityWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/TimerActivityWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TimerActivityWidget/CustomProgressViewStyle.swift b/TimerActivityWidget/CustomProgressViewStyle.swift new file mode 100644 index 0000000..8cc8d22 --- /dev/null +++ b/TimerActivityWidget/CustomProgressViewStyle.swift @@ -0,0 +1,28 @@ +// +// CustomProgressViewStyle.swift +// LiveRecipes +// +// Created by Сергей Мирошниченко on 12.05.2024. +// + +import SwiftUI + +struct CustomTimerViewStyle: ProgressViewStyle { + var progress: Int + + func makeBody(configuration: Configuration) -> some View { + GeometryReader { geometry in + ZStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 15) + .foregroundColor(Color(UIColor.white).opacity(1)) + .frame(width: geometry.size.width, height: 12) + .overlay(alignment: .leading){ + RoundedRectangle(cornerRadius: 15) + .foregroundColor(.orange) + .frame(width: configuration.fractionCompleted.map { $0 * (geometry.size.width)}, height: configuration.fractionCompleted! < 0.025 ? 6 : 12) + .animation(.linear, value: progress) + } + } + } + } +} diff --git a/TimerActivityWidget/Info.plist b/TimerActivityWidget/Info.plist new file mode 100644 index 0000000..0f118fb --- /dev/null +++ b/TimerActivityWidget/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/TimerActivityWidget/TimerActivityWidget.swift b/TimerActivityWidget/TimerActivityWidget.swift new file mode 100644 index 0000000..299579e --- /dev/null +++ b/TimerActivityWidget/TimerActivityWidget.swift @@ -0,0 +1,84 @@ +// +// TimerActivityWidget.swift +// TimerActivityWidget +// +// Created by Сергей Мирошниченко on 11.05.2024. +// + +import WidgetKit +import SwiftUI + +struct Provider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry(date: Date(), configuration: ConfigurationAppIntent()) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { + SimpleEntry(date: Date(), configuration: configuration) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + var entries: [SimpleEntry] = [] + + // Generate a timeline consisting of five entries an hour apart, starting from the current date. + let currentDate = Date() + for hourOffset in 0 ..< 5 { + let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! + let entry = SimpleEntry(date: entryDate, configuration: configuration) + entries.append(entry) + } + + return Timeline(entries: entries, policy: .atEnd) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let configuration: ConfigurationAppIntent +} + +struct TimerActivityWidgetEntryView : View { + var entry: Provider.Entry + + var body: some View { + VStack { + Text("Time:") + Text(entry.date, style: .time) + + Text("Favorite Emoji:") + Text(entry.configuration.favoriteEmoji) + } + } +} + +struct TimerActivityWidget: Widget { + let kind: String = "TimerActivityWidget" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in + TimerActivityWidgetEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } + } +} + +extension ConfigurationAppIntent { + fileprivate static var smiley: ConfigurationAppIntent { + let intent = ConfigurationAppIntent() + intent.favoriteEmoji = "😀" + return intent + } + + fileprivate static var starEyes: ConfigurationAppIntent { + let intent = ConfigurationAppIntent() + intent.favoriteEmoji = "🤩" + return intent + } +} + +#Preview(as: .systemSmall) { + TimerActivityWidget() +} timeline: { + SimpleEntry(date: .now, configuration: .smiley) + SimpleEntry(date: .now, configuration: .starEyes) +} diff --git a/TimerActivityWidget/TimerActivityWidgetBundle.swift b/TimerActivityWidget/TimerActivityWidgetBundle.swift new file mode 100644 index 0000000..3d2c78f --- /dev/null +++ b/TimerActivityWidget/TimerActivityWidgetBundle.swift @@ -0,0 +1,17 @@ +// +// TimerActivityWidgetBundle.swift +// TimerActivityWidget +// +// Created by Сергей Мирошниченко on 11.05.2024. +// + +import WidgetKit +import SwiftUI + +@main +struct TimerActivityWidgetBundle: WidgetBundle { + var body: some Widget { + TimerActivityWidget() + TimerActivityWidgetLiveActivity() + } +} diff --git a/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift b/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift new file mode 100644 index 0000000..f4e3a28 --- /dev/null +++ b/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift @@ -0,0 +1,125 @@ +// +// TimerActivityWidgetLiveActivity.swift +// TimerActivityWidget +// +// Created by Сергей Мирошниченко on 11.05.2024. +// + +import ActivityKit +import WidgetKit +import SwiftUI + + +struct TimerActivityWidgetLiveActivity: Widget { + + func getHours(_ seconds: Int) -> Int { + return seconds / 3600 + } + + func getMinutes(_ seconds: Int) -> Int { + return seconds / 60 + } + + func getSeconds(_ seconds: Int) -> Int { + return seconds % 60 + } + + var body: some WidgetConfiguration { + ActivityConfiguration(for: TimerAttributes.self) { context in + // Lock screen/banner UI goes here + HStack { + VStack(alignment: .leading) { + Text(context.attributes.dishName) + .font(.title2) + .foregroundStyle(.black) + .fontWeight(.medium) + HStack(spacing: 0) { + Text(context.state.currentStep) + .font(.title3) + .foregroundStyle(.orange) + .fontWeight(.medium) + Text("/\(context.state.stepsCount)") + .font(.title3) + .foregroundStyle(.orange) + .fontWeight(.medium) + } + } + Spacer() + VStack(alignment: .trailing) { + Image(systemName: "timer") + Spacer() + Text(getSeconds(context.state.timeRemaining) < 10 ? + "\(getMinutes(context.state.timeRemaining))m:0\(getSeconds(context.state.timeRemaining))s" : + "\(getMinutes(context.state.timeRemaining))m:\(getSeconds(context.state.timeRemaining))s" ) + .foregroundStyle(.orange) + .font(.title3) + .fontWeight(.medium) + + } + } + .padding() + .activityBackgroundTint(Color.white) + + } dynamicIsland: { context in + DynamicIsland { + // Expanded UI goes here. Compose the expanded UI through + // various regions, like leading/trailing/center/bottom + DynamicIslandExpandedRegion(.leading) { + HStack(spacing: 0){ + Text(context.state.currentStep) + .font(.title2) + .foregroundStyle(.white) + .fontWeight(.medium) + Text("/\(context.state.stepsCount)") + .font(.title2) + .foregroundStyle(.white) + .fontWeight(.medium) + } + .id(context.state) + } + + DynamicIslandExpandedRegion(.trailing) { + Text(getSeconds(context.state.timeRemaining) < 10 ? + "\(getMinutes(context.state.timeRemaining))m:0\(getSeconds(context.state.timeRemaining))s" : + "\(getMinutes(context.state.timeRemaining))m:\(getSeconds(context.state.timeRemaining))s" ) + .font(.title2) + .foregroundStyle(.orange) + .fontWeight(.semibold) + } + + DynamicIslandExpandedRegion(.bottom) { + HStack { + Image(systemName: "oven.fill") + .foregroundStyle(.orange) + .padding(.leading) + if let interval = context.state.interval { + + ProgressView(timerInterval: interval, countsDown: false) + } + +// ProgressView(value: Double(context.state.progress), total: Double(context.state.totalTime)) +// .progressViewStyle(CustomTimerViewStyle(progress: context.state.progress)) +// .padding(.top, 4) + + Image(systemName: "checkmark") + .foregroundStyle(.green) + .padding(.trailing) + } + } + + } compactLeading: { + Image(systemName: "oven.fill") + .foregroundStyle(.orange) + } compactTrailing: { + Text(getSeconds(context.state.timeRemaining) < 10 ? + "\(getMinutes(context.state.timeRemaining)):0\(getSeconds(context.state.timeRemaining))" : + "\(getMinutes(context.state.timeRemaining)):\(getSeconds(context.state.timeRemaining))" ) + .foregroundStyle(.orange) + } minimal: { + Image(systemName: "oven.fill") + .foregroundStyle(.orange) + } + } + } +} +