From 4920aa2e607915343e0c04b38e4db02dd0feea60 Mon Sep 17 00:00:00 2001 From: m1rosh Date: Wed, 8 May 2024 17:58:37 +0300 Subject: [PATCH 1/6] package ignore added --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1be66d6..177f9d7 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 From 4ad64818614fdc11f99ed75c07d0d8b37c659b09 Mon Sep 17 00:00:00 2001 From: m1rosh Date: Thu, 9 May 2024 01:35:07 +0300 Subject: [PATCH 2/6] localization completed --- LiveRecipes/Localizable.xcstrings | 956 ++++++++++++++---- .../BlinkingText + TimerView.swift | 4 +- .../DurationTextWithBlur.swift | 2 +- .../IngredientRunningTextView.swift | 49 +- .../AnimatedElements/StartCookingButton.swift | 55 +- LiveRecipes/Modules/Cooking/CookingView.swift | 8 +- LiveRecipes/Modules/Cooking/OneStepView.swift | 61 +- .../Cooking/PrepareForCookingView.swift | 36 +- .../Modules/OneDish/OneDishAssembly.swift | 2 +- .../Modules/OneDish/OneDishProtocols.swift | 8 +- LiveRecipes/Modules/OneDish/OneDishView.swift | 144 ++- .../Modules/OneDish/OneDishViewModel.swift | 6 +- .../NetworkService/Target/RecipeDTO.swift | 28 + 13 files changed, 1034 insertions(+), 325 deletions(-) diff --git a/LiveRecipes/Localizable.xcstrings b/LiveRecipes/Localizable.xcstrings index 5802873..c2c8e98 100644 --- a/LiveRecipes/Localizable.xcstrings +++ b/LiveRecipes/Localizable.xcstrings @@ -55,6 +55,57 @@ } } }, + "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.timeNeeded" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Time needed" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Понадобиться времени" + } + } + } + }, "cooktotime.error.message" : { "extractionState" : "manual", "localizations" : { @@ -650,6 +701,9 @@ } } }, + "Key" : { + "extractionState" : "manual" + }, "keywords.error.message" : { "extractionState" : "manual", "localizations" : { @@ -752,631 +806,1165 @@ } } }, - "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" : { "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" } } } }, - "settings.clearMyRecipes" : { + "oneStep.step11" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Clear my recipe" + "value" : "Step 11" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Очистить мои рецепты" + "value" : "Шаг 11" } } } }, - "settings.publishMyRecipe" : { + "oneStep.step12" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Publish my recipe" + "value" : "Step 12" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Опубликовать мой рецепт" + "value" : "Шаг 12" } } } }, - "settings.userSettings" : { + "oneStep.step13" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "User Settings" + "value" : "Step 13" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пользовательские насторойки" + "value" : "Шаг 13" } } } }, - "tab.cooking" : { + "oneStep.step14" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Cooking" + "state" : "translated", + "value" : "Step 14" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Готовка" + "value" : "Шаг 14" } } } }, - "tab.favorites" : { + "oneStep.step15" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Favorites" + "state" : "translated", + "value" : "Step 15" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сохраненные" + "value" : "Шаг 15" } } } }, - "tab.list" : { + "oneStep.step16" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "List" + "state" : "translated", + "value" : "Step 16" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Список" + "value" : "Шаг 16" } } } }, - "tab.recipes" : { + "oneStep.step17" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Recipes" + "state" : "translated", + "value" : "Step 17" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Рецепты" + "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" : "g" + "value" : "Step 19" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", - "value" : "г" + "state" : "translated", + "value" : "Шаг 19" } } } }, - "Готовка" : { - + "oneStep.step20" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Step 20" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шаг 20" + } + } + } }, - "Жиры" : { - + "oneStep.toMainPage" : { + "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" : { }, - "Кол-во порций: 5" : { - + "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" : "Очистить список покупок" + } + } + } }, - "Описание:" : { - + "settings.clearMyRecipes" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Clear my recipe" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очистить мои рецепты" + } + } + } }, - "Ошибка загрузки данных" : { - + "settings.publishMyRecipe" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Publish my recipe" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Опубликовать мой рецепт" + } + } + } }, - "Пищевая ценность на 100г" : { - + "settings.userSettings" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "User Settings" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пользовательские насторойки" + } + } + } }, - "Пищевая ценность:" : { - + "tab.cooking" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Cooking" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Готовка" + } + } + } }, - "Поехали!" : { - + "tab.favorites" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Favorites" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сохраненные" + } + } + } }, - "Понадобиться времени" : { - + "tab.list" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "List" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Список" + } + } + } }, - "Последний шаг" : { - + "tab.recipes" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Recipes" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Рецепты" + } + } + } }, - "Следующий шаг" : { + "Выберете рецепт и начните готовить!" : { }, - "Создать" : { - + "г" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "g" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "г" + } + } + } }, - "Состав:" : { - + "Гарнир" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Garnish" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Гарнир" + } + } + } }, - "Таймер" : { + "Готовка" : { }, - "Тут пока ничего нет" : { + "К рецептам" : { }, - "Углеводы" : { + "Ошибка загрузки данных" : { }, - "Цезарь" : { + "Создать" : { }, - "Цезарь с креветками" : { + "Таймер" : { }, - "Шаг %lld" : { + "Тут пока ничего нет" : { } }, diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift index 418fc98..2fc935b 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift @@ -113,7 +113,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 +129,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.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..9312da1 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) 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..35db923 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift @@ -11,6 +11,7 @@ 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 { @@ -19,17 +20,16 @@ struct StartCookingButton: View { }) { 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 +40,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/CookingView.swift b/LiveRecipes/Modules/Cooking/CookingView.swift index 57469d3..f56d8a2 100644 --- a/LiveRecipes/Modules/Cooking/CookingView.swift +++ b/LiveRecipes/Modules/Cooking/CookingView.swift @@ -18,10 +18,10 @@ struct CookingView: View { let _ = viewModel.findSteps() NoStepsView(tabSelected: tabSelected) } - else { - let _ = print(viewModel.steps.count) - OneStepView(viewModel: viewModel, stepNumber: 1) - } +// else { +// let _ = print(viewModel.steps.count) +// OneStepView(viewModel: viewModel, stepNumber: 1) +// } } } diff --git a/LiveRecipes/Modules/Cooking/OneStepView.swift b/LiveRecipes/Modules/Cooking/OneStepView.swift index a921f48..017c6bb 100644 --- a/LiveRecipes/Modules/Cooking/OneStepView.swift +++ b/LiveRecipes/Modules/Cooking/OneStepView.swift @@ -9,51 +9,67 @@ import SwiftUI import Swinject struct OneStepView: View { - @StateObject var viewModel: CookingViewModel + //@StateObject var viewModel: CookingViewModel @State var disconnectText = false var stepNumber: Int + var steps: [[String]] + var dishName: String + var dishType: String var body: some View { ScrollView { - HStack() { - Text("Цезарь с креветками") - .font(.system(size: 20, weight: .medium)) + HStack { + VStack(alignment: .leading) { + Text(dishName) + .font(.system(size: 20, weight: .medium)) + Text(dishType) + .font(.system(size: 16, weight: .light)) + } Spacer() } - .padding() - + .padding(.init(top: 0, leading: 16, bottom: 0, trailing: 10)) + VStack { VStack { - Image(viewModel.steps[stepNumber - 1]["image"] ?? "") - .resizable() - .frame(width: 370, height: 260) - Text(viewModel.steps[stepNumber - 1]["description"] ?? "") - .padding(.bottom) + 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() - TimerView(totalTime: 10, timeForProgress: 10, disconnectText: $disconnectText) - - if stepNumber + 1 <= viewModel.steps.count { + if steps[stepNumber - 1][2] != "0" { + TimerView(totalTime: Int(steps[stepNumber - 1][2]) ?? 0, timeForProgress: Int(steps[stepNumber - 1][2]) ?? 0, disconnectText: $disconnectText) + .padding(.bottom, 10) + } + if stepNumber + 1 <= steps.count { Button(action: { - print("nextStep") - }) { - NavigationLink(destination: OneStepView(viewModel: viewModel,stepNumber: stepNumber + 1)) { + }) { + NavigationLink(destination: OneStepView(stepNumber: stepNumber + 1, steps: steps, dishName: dishName, dishType: dishType)) { HStack { Spacer() - Text("Следующий шаг") + Text("oneStep.nextStep") .foregroundStyle(.white) .fontWeight(.semibold) - Spacer() Image(systemName: "chevron.right") .foregroundStyle(.white) - }.frame(width: 330, height: 35) + .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) } else { @@ -66,7 +82,7 @@ struct OneStepView: View { .resolve(RecipesView.self)}) { HStack { Spacer() - Text("На главную") + Text("oneStep.toMainPage") .foregroundStyle(.white) .fontWeight(.semibold) @@ -79,13 +95,14 @@ struct OneStepView: View { } .buttonStyle(.borderedProminent) .tint(.black) + .padding(.bottom, 20) } } } .gesture(TapGesture().onEnded({ disconnectText = true })) - .navigationTitle(stepNumber == viewModel.steps.count ? "Последний шаг" :"Шаг \(stepNumber)") + .navigationTitle(stepNumber == steps.count ? "oneStep.lastStep".localized :"oneStep.step\(stepNumber)".localized) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(stepNumber == 1 ? true : false) .toolbar(.visible, for: .tabBar) diff --git a/LiveRecipes/Modules/Cooking/PrepareForCookingView.swift b/LiveRecipes/Modules/Cooking/PrepareForCookingView.swift index f41435c..dc277fd 100644 --- a/LiveRecipes/Modules/Cooking/PrepareForCookingView.swift +++ b/LiveRecipes/Modules/Cooking/PrepareForCookingView.swift @@ -10,29 +10,26 @@ import Swinject struct PrepareForCookingView: View { @State private var animatedTextIndex = 0 - let texts = ["Креветки 4шт", "Салат 8 листов", "Сухарики 9 грамм", "Креветки 4 шт", "Соль по вкусу", "Перец по вкусу", - "Креветки 4шт", "Салат 8 листов", "Сухарики 9 грамм", "Креветки 4 шт", "Соль по вкусу", "Перец по вкусу"] - + @Environment(\.presentationMode) var presentationMode + //let texts = ["Креветки 4шт", "Салат 8 листов", "Сухарики 9 грамм", "Креветки 4 шт", "Соль по вкусу", "Перец по вкусу", + // "Креветки 4шт", "Салат 8 листов", "Сухарики 9 грамм", "Креветки 4 шт", "Соль по вкусу", "Перец по вкусу"] + let recipe: RecipeDTO + var body: some View { GeometryReader { geometry in VStack { - Text("Не забудьте перед готовкой") - .font(.system(size: 22, weight: .semibold)) - .foregroundStyle(.orange) 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..2c4d6ac 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 + //@State var tappedStatus: [Bool] = [false, false, false, false, false] 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") @@ -35,13 +41,13 @@ struct OneDishView: View { } Text(viewState.foundRecipe.name) - .font(.system(size: 22)) - .fontWeight(.medium) + .font(.system(size: 20)) + .fontWeight(.semibold) .padding(.bottom, 5) HStack { Image(systemName: "clock") .foregroundStyle(.orange) - Text("Время приготовления:") + Text("oneDish.timeToCook".localized) Text(viewState.foundRecipe.duration) Spacer() } @@ -49,7 +55,7 @@ struct OneDishView: View { HStack { Image(systemName: "fork.knife.circle") .foregroundStyle(.orange) - Text("Пищевая ценность:") + Text("oneDish.nutritionalValue".localized) Spacer() }.padding(.leading, 8) @@ -57,12 +63,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 +77,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 +119,7 @@ struct OneDishView: View { Divider() - Text("Пищевая ценность на 100г") + Text("oneDish.nutritionalValue100gr".localized) .font(.system(size: 13)) .padding(.bottom, 20) .foregroundStyle(Color(.systemGray)) @@ -121,70 +127,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 +201,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..67ac3c6 100644 --- a/LiveRecipes/Modules/OneDish/OneDishViewModel.swift +++ b/LiveRecipes/Modules/OneDish/OneDishViewModel.swift @@ -9,7 +9,7 @@ import Foundation final class OneDishViewModel: ObservableObject, OneDishViewModelProtocol { var model: OneDishModel - var id = 1 + var id = 90 @Published var foundRecipe = RecipeDTO(id: 0, name: "", bzy: BZY(calories: "0", protein: "0", fats: "0", carbohydrates: "0"), duration: "", photo: "", description: "", ingredients: [], steps: [[]], tag: "") init(oneDishModel: OneDishModel) { @@ -17,11 +17,9 @@ final class OneDishViewModel: ObservableObject, OneDishViewModelProtocol { 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/Services/NetworkService/Target/RecipeDTO.swift b/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift index b98a442..66f5beb 100644 --- a/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift +++ b/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift @@ -26,6 +26,34 @@ struct RecipeDTO: Codable, Hashable { var tag: String } +func getTranslation(_ dishType: String) -> String { + switch dishType { + case "dessert": + return "Дессерт".localized + case "drink": + return "Напиток".localized + case "snack": + return "Закуска".localized + case "garnish": + return "Гарнир".localized + case "first_dishes": + 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 From c8cff04fa40c6bb65031ee174adf1e7f70f38682 Mon Sep 17 00:00:00 2001 From: m1rosh Date: Mon, 13 May 2024 00:50:19 +0300 Subject: [PATCH 3/6] live activity and push notifications --- LiveRecipes.xcodeproj/project.pbxproj | 234 +++++++++++++++++- .../AppIcon.appiconset/Contents.json | 1 + .../AppIcon.appiconset/LRW.png | Bin 0 -> 25199 bytes .../ImageEN.imageset/Contents.json | 21 ++ .../ImageEN.imageset/frame3.png | Bin 0 -> 39457 bytes .../ImageRU.imageset/Contents.json | 21 ++ .../ImageRU.imageset/frame2.png | Bin 0 -> 48856 bytes LiveRecipes/Info.plist | 13 + LiveRecipes/Localizable.xcstrings | 148 ++++++++++- .../BlinkingText + TimerView.swift | 86 ++++++- .../DurationTextWithBlur.swift | 1 + .../AnimatedElements/StartCookingButton.swift | 3 +- .../Components/CustomTimerViewStyle.swift | 28 +++ .../Components/LiveActivityAttributes.swift | 24 ++ .../{ => Components}/NoStepsView.swift | 6 +- .../{ => Components}/OneStepView.swift | 83 +++++-- .../PrepareForCookingView.swift | 23 +- LiveRecipes/Modules/Cooking/CookingView.swift | 3 +- .../Modules/OneDish/OneDishAssembly.swift | 1 + .../Modules/OneDish/OneDishModel.swift | 4 +- .../Modules/OneDish/OneDishProtocols.swift | 2 +- LiveRecipes/Modules/OneDish/OneDishView.swift | 32 ++- .../Modules/OneDish/OneDishViewModel.swift | 4 +- LiveRecipes/Modules/Recipes/RecipesView.swift | 7 + .../NetworkService/Target/RecipeDTO.swift | 12 +- TimerActivityWidget/AppIntent.swift | 19 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../WidgetBackground.colorset/Contents.json | 11 + .../CustomProgressViewStyle.swift | 28 +++ TimerActivityWidget/Info.plist | 11 + TimerActivityWidget/TimerActivityWidget.swift | 84 +++++++ .../TimerActivityWidgetBundle.swift | 17 ++ .../TimerActivityWidgetLiveActivity.swift | 121 +++++++++ 35 files changed, 1019 insertions(+), 59 deletions(-) create mode 100644 LiveRecipes/Assets.xcassets/AppIcon.appiconset/LRW.png create mode 100644 LiveRecipes/Assets.xcassets/ImageEN.imageset/Contents.json create mode 100644 LiveRecipes/Assets.xcassets/ImageEN.imageset/frame3.png create mode 100644 LiveRecipes/Assets.xcassets/ImageRU.imageset/Contents.json create mode 100644 LiveRecipes/Assets.xcassets/ImageRU.imageset/frame2.png create mode 100644 LiveRecipes/Info.plist create mode 100644 LiveRecipes/Modules/Cooking/Components/CustomTimerViewStyle.swift create mode 100644 LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift rename LiveRecipes/Modules/Cooking/{ => Components}/NoStepsView.swift (87%) rename LiveRecipes/Modules/Cooking/{ => Components}/OneStepView.swift (60%) rename LiveRecipes/Modules/Cooking/{ => Components}/PrepareForCookingView.swift (67%) create mode 100644 TimerActivityWidget/AppIntent.swift create mode 100644 TimerActivityWidget/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 TimerActivityWidget/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 TimerActivityWidget/Assets.xcassets/Contents.json create mode 100644 TimerActivityWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json create mode 100644 TimerActivityWidget/CustomProgressViewStyle.swift create mode 100644 TimerActivityWidget/Info.plist create mode 100644 TimerActivityWidget/TimerActivityWidget.swift create mode 100644 TimerActivityWidget/TimerActivityWidgetBundle.swift create mode 100644 TimerActivityWidget/TimerActivityWidgetLiveActivity.swift diff --git a/LiveRecipes.xcodeproj/project.pbxproj b/LiveRecipes.xcodeproj/project.pbxproj index 3c5d250..8eccf52 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 = 65S7WB3693; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TimerActivityWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TimerActivityWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + 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 = 65S7WB3693; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = TimerActivityWidget/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TimerActivityWidget; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + 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 = { @@ -892,7 +1091,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -949,7 +1148,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -962,21 +1161,24 @@ 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; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"LiveRecipes/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 65S7WB3693; 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; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -995,21 +1197,24 @@ 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; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"LiveRecipes/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 65S7WB3693; 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; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -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 0000000000000000000000000000000000000000..be6eaca4cb4e8771ad7b20facf9461618950c62b GIT binary patch literal 25199 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 AI{*Lx literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..829c9c649ed3221c0f469c3059700315a5305051 GIT binary patch literal 39457 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 NQw~2?{nPu>{{h)Zvf2Ou literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8bd45b8debce51a8c7023f6b661f2b966dda46fc GIT binary patch literal 48856 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 b=D0DcI!ke5=UckN^ij~R(4AHO;uHS?16{M% literal 0 HcmV?d00001 diff --git a/LiveRecipes/Info.plist b/LiveRecipes/Info.plist new file mode 100644 index 0000000..dba7766 --- /dev/null +++ b/LiveRecipes/Info.plist @@ -0,0 +1,13 @@ + + + + + UIBackgroundModes + + audio + fetch + processing + remote-notification + + + diff --git a/LiveRecipes/Localizable.xcstrings b/LiveRecipes/Localizable.xcstrings index c2c8e98..76ee7ef 100644 --- a/LiveRecipes/Localizable.xcstrings +++ b/LiveRecipes/Localizable.xcstrings @@ -3,9 +3,24 @@ "strings" : { "" : { + }, + " %lld" : { + + }, + " дн " : { + + }, + " мин " : { + + }, + " ч " : { + }, "%@" : { + }, + "%lld" : { + }, "%lldh:%lldm:%llds" : { "localizations" : { @@ -55,6 +70,23 @@ } } }, + "cookingPrepare.days" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " d. " + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : " д. " + } + } + } + }, "cookingPrepare.dontForget" : { "extractionState" : "stale", "localizations" : { @@ -89,6 +121,40 @@ } } }, + "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" : { @@ -513,6 +579,9 @@ } } } + }, + "Delete" : { + }, "filters" : { "extractionState" : "manual", @@ -701,6 +770,23 @@ } } }, + "Image" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "ImageEN" + } + }, + "ru" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "ImageRU" + } + } + } + }, "Key" : { "extractionState" : "manual" }, @@ -1028,6 +1114,7 @@ } }, "oneStep.nextStep" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1384,6 +1471,7 @@ } }, "oneStep.toMainPage" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -1741,6 +1829,9 @@ }, "Select a segment" : { + }, + "Set time for Timer" : { + }, "settings" : { "extractionState" : "manual", @@ -1912,6 +2003,57 @@ } } }, + "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" : " завершен" + } + } + } + }, "Выберете рецепт и начните готовить!" : { }, @@ -1951,6 +2093,9 @@ }, "Готовка" : { + }, + "Готово!" : { + }, "К рецептам" : { @@ -1960,9 +2105,6 @@ }, "Создать" : { - }, - "Таймер" : { - }, "Тут пока ничего нет" : { diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift index 2fc935b..7fbd4f1 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,53 @@ 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(using: 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) + 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) + + do { + currentActivity = try Activity.request(attributes: attributes, contentState: state) + } + catch { + print(error.localizedDescription) + } + } + var body: some View { let (hours, minutes, seconds) = minutesAndSeconds @@ -60,7 +116,7 @@ struct TimerView: View { isTimerRunning = false progress = 0 timeForProgress = totalTime - disconnectText = false + stopActivity() }) { Image(systemName: "arrow.counterclockwise") .imageScale(.large) @@ -68,14 +124,20 @@ struct TimerView: View { Spacer() - Text("Таймер") + Text("timer".localized) + Image(systemName: "timer") Spacer() Button(action: { isTimerRunning.toggle() - + if activityStarted { + updateActivity() + } + else { + startActivity() + } }) { Image(systemName: isTimerRunning ? "pause.fill" : "play.fill") .imageScale(.large) @@ -87,7 +149,10 @@ struct TimerView: View { ProgressView(value: Double(progress), total: Double(totalTime)) .progressViewStyle(CustomProgressViewStyle(progress: $progress)) if timeForProgress == 0 { - BlinkingText(text: "Готово", disconnectTimer: $disconnectText) + Text("Готово!") + .foregroundColor(.white) + .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 +165,14 @@ struct TimerView: View { if progress < totalTime { progress += 1 timeForProgress -= 1 - + updateActivity() } + if progress == totalTime { - disconnectText = false isTimerRunning = false + updateActivity() + stopActivity() + notify() } } } @@ -129,7 +197,7 @@ struct CustomProgressViewStyle: ProgressViewStyle { .overlay(alignment: .leading){ RoundedRectangle(cornerRadius: 15) .foregroundColor(.orange) - .frame(width: configuration.fractionCompleted.map { $0 * geometry.size.width }, height: configuration.fractionCompleted! < 0.025 ? 15 : 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 9312da1..0af5673 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/DurationTextWithBlur.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/DurationTextWithBlur.swift @@ -29,6 +29,7 @@ struct DurationTextWithBlur: View { self.blurRadius = 0 } } + Text(text) .font(.largeTitle) .foregroundColor(.orange) diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift index 35db923..243e1c4 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/StartCookingButton.swift @@ -10,14 +10,13 @@ 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:{ OneStepView(stepNumber: 1, steps: transferData.steps, dishName: transferData.name, dishType: getTranslation(transferData.tag)) 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..652b84b --- /dev/null +++ b/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift @@ -0,0 +1,24 @@ +// +// 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 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/OneStepView.swift b/LiveRecipes/Modules/Cooking/Components/OneStepView.swift similarity index 60% rename from LiveRecipes/Modules/Cooking/OneStepView.swift rename to LiveRecipes/Modules/Cooking/Components/OneStepView.swift index 017c6bb..a243d1e 100644 --- a/LiveRecipes/Modules/Cooking/OneStepView.swift +++ b/LiveRecipes/Modules/Cooking/Components/OneStepView.swift @@ -7,16 +7,24 @@ import SwiftUI import Swinject +import ActivityKit + struct OneStepView: View { //@StateObject var viewModel: CookingViewModel - @State var disconnectText = false + @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) { @@ -28,7 +36,7 @@ struct OneStepView: View { 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) { @@ -45,8 +53,25 @@ struct OneStepView: View { .padding() if steps[stepNumber - 1][2] != "0" { - TimerView(totalTime: Int(steps[stepNumber - 1][2]) ?? 0, timeForProgress: Int(steps[stepNumber - 1][2]) ?? 0, disconnectText: $disconnectText) - .padding(.bottom, 10) + 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: { @@ -55,7 +80,7 @@ struct OneStepView: View { NavigationLink(destination: OneStepView(stepNumber: stepNumber + 1, steps: steps, dishName: dishName, dishType: dishType)) { HStack { Spacer() - Text("oneStep.nextStep") + Text("oneStep.nextStep".localized) .foregroundStyle(.white) .fontWeight(.semibold) Image(systemName: "chevron.right") @@ -70,6 +95,7 @@ struct OneStepView: View { .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 { @@ -82,30 +108,59 @@ struct OneStepView: View { .resolve(RecipesView.self)}) { HStack { Spacer() - Text("oneStep.toMainPage") + Text("oneStep.toMainPage".localized) .foregroundStyle(.white) .fontWeight(.semibold) Image(systemName: "fork.knife") .foregroundStyle(.white) + .frame(height: 35) Spacer() - }.frame(width: 330, height: 35) + } } - + } .buttonStyle(.borderedProminent) - .tint(.black) - .padding(.bottom, 20) + .tint(.black) + .padding(.bottom, 20) + .frame(width: UIScreen.main.bounds.width - 20) + .offset(y: isTimerOnView ? 10 : -100) } } } - .gesture(TapGesture().onEnded({ - disconnectText = true - })) - .navigationTitle(stepNumber == steps.count ? "oneStep.lastStep".localized :"oneStep.step\(stepNumber)".localized) + .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/PrepareForCookingView.swift b/LiveRecipes/Modules/Cooking/Components/PrepareForCookingView.swift similarity index 67% rename from LiveRecipes/Modules/Cooking/PrepareForCookingView.swift rename to LiveRecipes/Modules/Cooking/Components/PrepareForCookingView.swift index dc277fd..3af3674 100644 --- a/LiveRecipes/Modules/Cooking/PrepareForCookingView.swift +++ b/LiveRecipes/Modules/Cooking/Components/PrepareForCookingView.swift @@ -10,9 +10,9 @@ import Swinject struct PrepareForCookingView: View { @State private var animatedTextIndex = 0 + @State private var durationText = "" @Environment(\.presentationMode) var presentationMode - //let texts = ["Креветки 4шт", "Салат 8 листов", "Сухарики 9 грамм", "Креветки 4 шт", "Соль по вкусу", "Перец по вкусу", - // "Креветки 4шт", "Салат 8 листов", "Сухарики 9 грамм", "Креветки 4 шт", "Соль по вкусу", "Перец по вкусу"] + let recipe: RecipeDTO var body: some View { @@ -32,6 +32,22 @@ struct PrepareForCookingView: View { } }.frame(minWidth: geometry.size.width, minHeight: geometry.size.height * 0.7) .scrollIndicators(.hidden) + .onAppear { + if recipe.decomposeDuration().0 != 0 { + durationText += " \(recipe.decomposeDuration().0)" + durationText += "cookingPrepare.days".localized + durationText += " \(recipe.decomposeDuration().1)" + durationText += "cookingPrepare.hours".localized + durationText += " \(recipe.decomposeDuration().2)" + durationText += "cookingPrepare.minutes".localized + } + else { + durationText += " \(recipe.decomposeDuration().1)" + durationText += "cookingPrepare.hours".localized + durationText += " \(recipe.decomposeDuration().2)" + durationText += "cookingPrepare.minutes".localized + } + } TransperentBlur() .blur(radius: 10) .frame(height: 70) @@ -39,7 +55,8 @@ struct PrepareForCookingView: View { } - DurationTextWithBlur(text: recipe.duration) + + DurationTextWithBlur(text: durationText) StartCookingButton(transferData: recipe) }.background(RadialGradient(gradient: Gradient(colors: [.orange, .white]), center: .center, startRadius: 50, endRadius: 400) diff --git a/LiveRecipes/Modules/Cooking/CookingView.swift b/LiveRecipes/Modules/Cooking/CookingView.swift index f56d8a2..e4c0883 100644 --- a/LiveRecipes/Modules/Cooking/CookingView.swift +++ b/LiveRecipes/Modules/Cooking/CookingView.swift @@ -15,14 +15,13 @@ struct CookingView: View { let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { if viewModel.steps.count == 0 { - let _ = viewModel.findSteps() NoStepsView(tabSelected: tabSelected) } + } // else { // let _ = print(viewModel.steps.count) // OneStepView(viewModel: viewModel, stepNumber: 1) // } - } } diff --git a/LiveRecipes/Modules/OneDish/OneDishAssembly.swift b/LiveRecipes/Modules/OneDish/OneDishAssembly.swift index 87c96f3..9b904ef 100644 --- a/LiveRecipes/Modules/OneDish/OneDishAssembly.swift +++ b/LiveRecipes/Modules/OneDish/OneDishAssembly.swift @@ -7,6 +7,7 @@ import Foundation import Swinject +import SwiftUI final class OneDishAssembly: Assembly { func assemble(container: Swinject.Container) { diff --git a/LiveRecipes/Modules/OneDish/OneDishModel.swift b/LiveRecipes/Modules/OneDish/OneDishModel.swift index 43a45ad..cc3e96b 100644 --- a/LiveRecipes/Modules/OneDish/OneDishModel.swift +++ b/LiveRecipes/Modules/OneDish/OneDishModel.swift @@ -9,7 +9,7 @@ import Foundation final class OneDishModel: ObservableObject, OneDishModelProtocol { let recipeAPI = RecipeAPI() - @Published var foundRecipe = RecipeDTO(id: 0, name: "", bzy: BZY(calories: "0", protein: "0", fats: "0", carbohydrates: "0"), duration: "", photo: "", description: "", ingredients: [], steps: [[]], tag: "") + @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(recipeAPI: RecipeAPI) { // self.recipeAPI = recipeAPI @@ -22,7 +22,7 @@ final class OneDishModel: ObservableObject, OneDishModelProtocol { case .success(let result): completion(result) case .failure(_): - completion(RecipeDTO(id: 0, name: "", bzy: BZY(calories: "0", protein: "0", fats: "0", carbohydrates: "0"), duration: "", photo: "", description: "", ingredients: [], steps: [[]], tag: "")) + completion(RecipeDTO(id: 0, name: "", bzy: BZY(calories: "0", protein: "0", fats: "0", carbohydrates: "0"), duration: 0, photo: "", description: "", ingredients: [], steps: [[]], tag: "")) } }) } diff --git a/LiveRecipes/Modules/OneDish/OneDishProtocols.swift b/LiveRecipes/Modules/OneDish/OneDishProtocols.swift index 9a3ced9..5b1735f 100644 --- a/LiveRecipes/Modules/OneDish/OneDishProtocols.swift +++ b/LiveRecipes/Modules/OneDish/OneDishProtocols.swift @@ -8,7 +8,7 @@ import Foundation protocol OneDishModelProtocol { - func findRecipe(id: Int, completion: @escaping (RecipeDTO)->Void) + func findRecipe(id: Int, completion: @escaping (RecipeDTO) -> Void) } protocol OneDishViewModelProtocol { diff --git a/LiveRecipes/Modules/OneDish/OneDishView.swift b/LiveRecipes/Modules/OneDish/OneDishView.swift index 2c4d6ac..30b291a 100644 --- a/LiveRecipes/Modules/OneDish/OneDishView.swift +++ b/LiveRecipes/Modules/OneDish/OneDishView.swift @@ -12,12 +12,12 @@ 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 - //@State var tappedStatus: [Bool] = [false, false, false, false, false] - var openedFromRecipesView: Bool = true + var openedFromRecipesView: Bool = true var body: some View { if viewState.foundRecipe.ingredients.isEmpty { @@ -40,15 +40,31 @@ struct OneDishView: View { .frame(width: 370, height: 260) } - Text(viewState.foundRecipe.name) - .font(.system(size: 20)) - .fontWeight(.semibold) - .padding(.bottom, 5) + Text(viewState.foundRecipe.name) + .font(.system(size: 20)) + .fontWeight(.semibold) + .padding(.bottom, 5) + HStack { Image(systemName: "clock") .foregroundStyle(.orange) - Text("oneDish.timeToCook".localized) - 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) diff --git a/LiveRecipes/Modules/OneDish/OneDishViewModel.swift b/LiveRecipes/Modules/OneDish/OneDishViewModel.swift index 67ac3c6..40eb723 100644 --- a/LiveRecipes/Modules/OneDish/OneDishViewModel.swift +++ b/LiveRecipes/Modules/OneDish/OneDishViewModel.swift @@ -9,8 +9,8 @@ import Foundation final class OneDishViewModel: ObservableObject, OneDishViewModelProtocol { var model: OneDishModel - var id = 90 - @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 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/RecipeDTO.swift b/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift index 66f5beb..610acd2 100644 --- a/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift +++ b/LiveRecipes/Services/NetworkService/Target/RecipeDTO.swift @@ -18,12 +18,20 @@ 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 { @@ -36,7 +44,7 @@ func getTranslation(_ dishType: String) -> String { return "Закуска".localized case "garnish": return "Гарнир".localized - case "first_dishes": + case "firstDish": return "Первое блюдо".localized case "salad": return "Салат".localized 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..363e082 --- /dev/null +++ b/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift @@ -0,0 +1,121 @@ +// +// 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) + + 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) + } + } + } +} + From 91327e25046db9635745c72ee0e5d5e9c2605944 Mon Sep 17 00:00:00 2001 From: m1rosh Date: Mon, 13 May 2024 02:40:02 +0300 Subject: [PATCH 4/6] api url change --- LiveRecipes.xcodeproj/project.pbxproj | 20 +++++++++---------- .../NetworkService/Target/Recipe.swift | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/LiveRecipes.xcodeproj/project.pbxproj b/LiveRecipes.xcodeproj/project.pbxproj index 8eccf52..634de09 100644 --- a/LiveRecipes.xcodeproj/project.pbxproj +++ b/LiveRecipes.xcodeproj/project.pbxproj @@ -990,12 +990,12 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 65S7WB3693; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TimerActivityWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TimerActivityWidget; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1018,12 +1018,12 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 65S7WB3693; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TimerActivityWidget/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TimerActivityWidget; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1091,7 +1091,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1148,7 +1148,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -1168,7 +1168,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"LiveRecipes/Preview Content\""; - DEVELOPMENT_TEAM = 65S7WB3693; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LiveRecipes/Info.plist; @@ -1178,7 +1178,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1204,7 +1204,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"LiveRecipes/Preview Content\""; - DEVELOPMENT_TEAM = 65S7WB3693; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = LiveRecipes/Info.plist; @@ -1214,7 +1214,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", 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 { From ffca8e26a45f60c880f74106dab0565f0d3b4389 Mon Sep 17 00:00:00 2001 From: m1rosh Date: Mon, 13 May 2024 16:44:44 +0300 Subject: [PATCH 5/6] gitignore update --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 177f9d7..4d75a13 100644 --- a/.gitignore +++ b/.gitignore @@ -42,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 From 5a112fb1efb74626d1d1fc2e984d341004ad6ebf Mon Sep 17 00:00:00 2001 From: m1rosh Date: Mon, 13 May 2024 18:06:57 +0300 Subject: [PATCH 6/6] timer activity change --- LiveRecipes/Info.plist | 7 +----- .../BlinkingText + TimerView.swift | 24 +++++++++++-------- .../Components/LiveActivityAttributes.swift | 1 + .../TimerActivityWidgetLiveActivity.swift | 10 +++++--- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/LiveRecipes/Info.plist b/LiveRecipes/Info.plist index dba7766..666cc76 100644 --- a/LiveRecipes/Info.plist +++ b/LiveRecipes/Info.plist @@ -3,11 +3,6 @@ UIBackgroundModes - - audio - fetch - processing - remote-notification - + diff --git a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift index 7fbd4f1..b83658b 100644 --- a/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift +++ b/LiveRecipes/Modules/Cooking/AnimatedElements/BlinkingText + TimerView.swift @@ -78,15 +78,17 @@ struct TimerView: View { func stopActivity() { Task { - await currentActivity?.end(using: nil, dismissalPolicy: .after(Date().addingTimeInterval(10))) + 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) - await currentActivity?.update(using: state) + 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) } } @@ -97,10 +99,11 @@ struct TimerView: View { 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) + 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, contentState: state) + currentActivity = try Activity.request(attributes: attributes, content: content) } catch { print(error.localizedDescription) @@ -133,10 +136,11 @@ struct TimerView: View { Button(action: { isTimerRunning.toggle() if activityStarted { - updateActivity() + // updateActivity() } else { startActivity() + //progress = timeForProgress } }) { Image(systemName: isTimerRunning ? "pause.fill" : "play.fill") @@ -146,11 +150,11 @@ 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 { Text("Готово!") - .foregroundColor(.white) + .foregroundColor(.orange) .font(.system(size: 19, weight: .semibold)) .padding(.top, 2) } @@ -165,12 +169,12 @@ struct TimerView: View { if progress < totalTime { progress += 1 timeForProgress -= 1 - updateActivity() + //updateActivity() } if progress == totalTime { isTimerRunning = false - updateActivity() + //updateActivity() stopActivity() notify() } diff --git a/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift b/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift index 652b84b..17bc5b6 100644 --- a/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift +++ b/LiveRecipes/Modules/Cooking/Components/LiveActivityAttributes.swift @@ -18,6 +18,7 @@ public struct TimerAttributes: ActivityAttributes { var timeRemaining: Int var currentStep: String var stepsCount: Int + var interval: ClosedRange? } var dishName: String diff --git a/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift b/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift index 363e082..f4e3a28 100644 --- a/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift +++ b/TimerActivityWidget/TimerActivityWidgetLiveActivity.swift @@ -92,10 +92,14 @@ struct TimerActivityWidgetLiveActivity: Widget { 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) +// 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)