diff --git a/Mayday.xcodeproj/project.pbxproj b/Mayday.xcodeproj/project.pbxproj index 71139e8..ca40268 100644 --- a/Mayday.xcodeproj/project.pbxproj +++ b/Mayday.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ AA000001000030 /* MaydayLiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000030 /* MaydayLiveActivityBundle.swift */; }; AA000001000031 /* MaydayLiveActivityLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000031 /* MaydayLiveActivityLiveActivity.swift */; }; AA000001000032 /* AlertAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000008 /* AlertAttributes.swift */; }; + AA000001000033 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000027 /* Localizable.xcstrings */; }; AA000007000001 /* MaydayLiveActivity.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AA000008000001 /* MaydayLiveActivity.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -332,6 +333,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + AA000001000033 /* Localizable.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -446,7 +448,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = WA8SWY233K; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = MaydayLiveActivity/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 17.0; @@ -470,7 +472,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = WA8SWY233K; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = MaydayLiveActivity/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 17.0; diff --git a/Mayday.xcodeproj/xcshareddata/xcschemes/Mayday.xcscheme b/Mayday.xcodeproj/xcshareddata/xcschemes/Mayday.xcscheme new file mode 100644 index 0000000..cbea70a --- /dev/null +++ b/Mayday.xcodeproj/xcshareddata/xcschemes/Mayday.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mayday.xcodeproj/xcshareddata/xcschemes/MaydayLiveActivity.xcscheme b/Mayday.xcodeproj/xcshareddata/xcschemes/MaydayLiveActivity.xcscheme new file mode 100644 index 0000000..0b66040 --- /dev/null +++ b/Mayday.xcodeproj/xcshareddata/xcschemes/MaydayLiveActivity.xcscheme @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mayday/Assets.xcassets/AccentColor.colorset/Contents.json b/Mayday/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Mayday/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Mayday/InfoPlist.xcstrings b/Mayday/InfoPlist.xcstrings index 60e994a..9e598e5 100644 --- a/Mayday/InfoPlist.xcstrings +++ b/Mayday/InfoPlist.xcstrings @@ -1,19 +1,32 @@ { "sourceLanguage" : "ru", "strings" : { - "NSUserNotificationsUsageDescription" : { + "CFBundleName" : { + "comment" : "Bundle name", + "extractionState" : "extracted_with_value", "localizations" : { "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Mayday использует уведомления для оповещения о критических событиях." + "state" : "new", + "value" : "Mayday" } - }, + } + } + }, + "NSUserNotificationsUsageDescription" : { + "extractionState" : "stale", + "localizations" : { "en" : { "stringUnit" : { "state" : "translated", "value" : "Mayday uses notifications to alert you about critical events." } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mayday использует уведомления для оповещения о критических событиях." + } } } } diff --git a/Mayday/Localizable.xcstrings b/Mayday/Localizable.xcstrings index e9aa136..7165986 100644 --- a/Mayday/Localizable.xcstrings +++ b/Mayday/Localizable.xcstrings @@ -1,978 +1,1011 @@ { "sourceLanguage" : "ru", "strings" : { - "login_subtitle" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Мониторинг и уведомления" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Monitoring and notifications" - } - } - } + "" : { + }, - "password" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Password" - } - } - } + "(%lld)" : { + }, - "login_button" : { + "%@ alert: %@" : { "localizations" : { "ru" : { "stringUnit" : { - "state" : "translated", - "value" : "Войти" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in" - } - } - } - }, - "login_no_account" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет аккаунта? Зарегистрироваться" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No account? Sign up" - } - } - } - }, - "demo_mode" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Демо-режим" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Demo mode" - } - } - } - }, - "register_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Регистрация" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Registration" - } - } - } - }, - "confirm_password" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Подтвердите пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Confirm password" - } - } - } - }, - "password_min_length" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Пароль должен содержать не менее 8 символов" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Password must be at least 8 characters" - } - } - } - }, - "passwords_mismatch" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Пароли не совпадают" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Passwords don't match" - } - } - } - }, - "register_button" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Создать аккаунт" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Create account" - } - } - } - }, - "register_has_account" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Уже есть аккаунт?" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Already have an account?" - } - } - } - }, - "verify_email_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Подтвердите email" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verify email" - } - } - } - }, - "verify_code_sent_to" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Код отправлен на" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Code sent to" - } - } - } - }, - "verify_resend_cooldown %lld" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Отправить повторно (%lld сек)" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resend (%lld sec)" - } - } - } - }, - "verify_resend" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Отправить повторно" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resend" - } - } - } - }, - "verify_nav_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Подтверждение" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verification" - } - } - } - }, - "loading_error" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ошибка загрузки" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading error" - } - } - } - }, - "no_notifications" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет уведомлений" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No notifications" - } - } - } - }, - "no_notifications_description" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Новые уведомления появятся здесь" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New notifications will appear here" - } - } - } - }, - "notifications_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Уведомления" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Notifications" - } - } - } - }, - "demo_badge" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "ДЕМО" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "DEMO" - } - } - } - }, - "notifications_active" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Активные" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Active" - } - } - } - }, - "notifications_completed" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Завершённые" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Completed" - } - } - } - }, - "open_button" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Открыть" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open" - } - } - } - }, - "mark_as_read" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Отметить прочитанным" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mark as read" - } - } - } - }, - "details_section" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Подробности" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Details" - } - } - } - }, - "info_section" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Информация" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Information" - } - } - } - }, - "status_section" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Статус" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Status" - } - } - } - }, - "status_read" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Прочитано" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Read" - } - } - } - }, - "status_new" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Новое" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New" - } - } - } - }, - "channel_in_app" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "В приложении" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "In-app" - } - } - } - }, - "channel_label" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Канал" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Channel" - } - } - } - }, - "received_label" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Получено" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Received" - } - } - } - }, - "read_at_label" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Прочитано" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Read" - } - } - } - }, - "notification_read_at %@" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "прочитано %@" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "read %@" + "state" : "new", + "value" : "%1$@ alert: %2$@" } } } }, "account_section" : { "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Аккаунт" - } - }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Account" } - } - } - }, - "change_password" : { - "localizations" : { + }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сменить пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Change password" + "value" : "Аккаунт" } } } }, - "push_notifications" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Push-уведомления" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Push notifications" - } - } - } + "Active" : { + }, "active_sessions" : { "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Активные сессии" - } - }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Active sessions" } - } - } - }, - "logout_button" : { - "localizations" : { + }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выйти из аккаунта" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out" - } - } - } - }, - "logout_all_button" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Выйти на всех устройствах" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out on all devices" - } - } - } - }, - "settings_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Настройки" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" - } - } - } - }, - "done_button" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Готово" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Done" - } - } - } - }, - "logout_all_confirm" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Выйти на всех устройствах?" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out on all devices?" - } - } - } - }, - "logout_all_action" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Выйти везде" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out everywhere" - } - } - } - }, - "cancel" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Отмена" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel" - } - } - } - }, - "error_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ошибка" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Error" - } - } - } - }, - "current_password" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Текущий пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Current password" - } - } - } - }, - "new_password" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Новый пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New password" - } - } - } - }, - "confirm_new_password" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Подтвердите новый пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Confirm new password" - } - } - } - }, - "save_button" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Сохранить" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Save" - } - } - } - }, - "change_password_title" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Сменить пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Change password" - } - } - } - }, - "current_session" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Текущая" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Current" - } - } - } - }, - "session_created %@" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Создана: %@" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Created: %@" - } - } - } - }, - "delete_button" : { - "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Удалить" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete" + "value" : "Активные сессии" } } } }, "active_sessions_title" : { "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Активные сессии" - } - }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Active sessions" } - } - } - }, - "password_changed_success" : { - "localizations" : { + }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Пароль успешно изменён" + "value" : "Активные сессии" } - }, + } + } + }, + "alert_status_active" : { + "extractionState" : "stale", + "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Password changed successfully" + "value" : "active" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "активен" + } + } + } + }, + "alert_status_resolved" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "resolved" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "завершён" + } + } + } + }, + "cancel" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отмена" + } + } + } + }, + "change_password" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Change password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сменить пароль" + } + } + } + }, + "change_password_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Change password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сменить пароль" + } + } + } + }, + "channel_in_app" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "In-app" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "В приложении" + } + } + } + }, + "channel_label" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Channel" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Канал" + } + } + } + }, + "confirm_new_password" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm new password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подтвердите новый пароль" + } + } + } + }, + "confirm_password" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подтвердите пароль" + } + } + } + }, + "current_password" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Current password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Текущий пароль" + } + } + } + }, + "current_session" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Current" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Текущая" + } + } + } + }, + "delete_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Удалить" + } + } + } + }, + "demo_badge" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "DEMO" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "ДЕМО" + } + } + } + }, + "demo_mode" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Demo mode" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Демо-режим" + } + } + } + }, + "details_section" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Details" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подробности" + } + } + } + }, + "done_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Done" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Готово" + } + } + } + }, + "Email" : { + + }, + "error_invalid_credentials" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid email or password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Неверный email или пароль" } } } }, "error_invalid_url" : { "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Неверный URL" - } - }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Invalid URL" } - } - } - }, - "error_invalid_credentials" : { - "localizations" : { + }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Неверный email или пароль" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Invalid email or password" + "value" : "Неверный URL" } } } }, - "alert_status_active" : { + "error_title" : { "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "активен" - } - }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "active" + "value" : "Error" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка" } } } }, - "alert_status_resolved" : { + "info_section" : { "localizations" : { - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "завершён" - } - }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "resolved" + "value" : "Information" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Информация" + } + } + } + }, + "loading_error" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading error" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка загрузки" + } + } + } + }, + "login_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sign in" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Войти" + } + } + } + }, + "login_no_account" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No account? Sign up" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет аккаунта? Зарегистрироваться" + } + } + } + }, + "login_subtitle" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Monitoring and notifications" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Мониторинг и уведомления" + } + } + } + }, + "logout_all_action" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sign out everywhere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выйти везде" + } + } + } + }, + "logout_all_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sign out on all devices" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выйти на всех устройствах" + } + } + } + }, + "logout_all_confirm" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sign out on all devices?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выйти на всех устройствах?" + } + } + } + }, + "logout_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sign out" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выйти из аккаунта" + } + } + } + }, + "mark_as_read" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mark as read" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отметить прочитанным" + } + } + } + }, + "Mayday" : { + + }, + "new_password" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новый пароль" + } + } + } + }, + "no_notifications" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No notifications" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет уведомлений" + } + } + } + }, + "no_notifications_description" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New notifications will appear here" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новые уведомления появятся здесь" + } + } + } + }, + "notification_read_at %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "read %@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "прочитано %@" + } + } + } + }, + "notifications_active" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Active" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Активные" + } + } + } + }, + "notifications_completed" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Completed" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Завершённые" + } + } + } + }, + "notifications_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уведомления" + } + } + } + }, + "OK" : { + + }, + "open_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открыть" + } + } + } + }, + "password" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль" + } + } + } + }, + "password_changed_success" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password changed successfully" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль успешно изменён" + } + } + } + }, + "password_min_length" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password must be at least 8 characters" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль должен содержать не менее 8 символов" + } + } + } + }, + "passwords_mismatch" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passwords don't match" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароли не совпадают" + } + } + } + }, + "push_notifications" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push notifications" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-уведомления" + } + } + } + }, + "read_at_label" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Read" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прочитано" + } + } + } + }, + "received_label" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Received" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Получено" + } + } + } + }, + "register_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create account" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Создать аккаунт" + } + } + } + }, + "register_has_account" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Already have an account?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уже есть аккаунт?" + } + } + } + }, + "register_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Регистрация" + } + } + } + }, + "Resolved" : { + + }, + "save_button" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Save" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сохранить" + } + } + } + }, + "session_created %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Created: %@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Создана: %@" + } + } + } + }, + "settings_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Settings" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Настройки" + } + } + } + }, + "status_new" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Новое" + } + } + } + }, + "status_read" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Read" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Прочитано" + } + } + } + }, + "status_section" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Status" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Статус" + } + } + } + }, + "verify_code_sent_to" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Code sent to" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Код отправлен на" + } + } + } + }, + "verify_email_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verify email" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подтвердите email" + } + } + } + }, + "verify_nav_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verification" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Подтверждение" + } + } + } + }, + "verify_resend" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resend" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправить повторно" + } + } + } + }, + "verify_resend_cooldown %lld" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resend (%lld sec)" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправить повторно (%lld сек)" } } } diff --git a/Mayday/Views/Notifications/NotificationDetailView.swift b/Mayday/Views/Notifications/NotificationDetailView.swift index 7bf1274..a5c7b6f 100644 --- a/Mayday/Views/Notifications/NotificationDetailView.swift +++ b/Mayday/Views/Notifications/NotificationDetailView.swift @@ -58,7 +58,7 @@ struct NotificationDetailView: View { VStack(spacing: 16) { ZStack { Circle() - .fill(.white) + .fill(Color(.secondarySystemGroupedBackground)) .frame(width: 88, height: 88) .shadow(color: topicColor.opacity(0.3), radius: 12, y: 4) Circle() @@ -115,7 +115,7 @@ struct NotificationDetailView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) - .background(.white) + .background(Color(.secondarySystemGroupedBackground)) .clipShape(RoundedRectangle(cornerRadius: 16)) .shadow(color: .black.opacity(0.04), radius: 6, y: 2) } @@ -145,7 +145,7 @@ struct NotificationDetailView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) - .background(.white) + .background(Color(.secondarySystemGroupedBackground)) .clipShape(RoundedRectangle(cornerRadius: 16)) .shadow(color: .black.opacity(0.04), radius: 6, y: 2) } @@ -186,7 +186,7 @@ struct NotificationDetailView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(16) - .background(.white) + .background(Color(.secondarySystemGroupedBackground)) .clipShape(RoundedRectangle(cornerRadius: 16)) .shadow(color: .black.opacity(0.04), radius: 6, y: 2) } diff --git a/Mayday/Views/Notifications/NotificationsView.swift b/Mayday/Views/Notifications/NotificationsView.swift index b298201..0ed61b3 100644 --- a/Mayday/Views/Notifications/NotificationsView.swift +++ b/Mayday/Views/Notifications/NotificationsView.swift @@ -40,13 +40,14 @@ struct NotificationsView: View { #if DEBUG if PreviewData.isPreviewMode { ToolbarItem(placement: .topBarLeading) { - Text("demo_badge") - .font(.caption2.bold()) - .foregroundStyle(.white) - .padding(.horizontal, 8) - .padding(.vertical, 3) - .background(.orange) - .clipShape(Capsule()) + Button(action: {}) { + Text("demo_badge") + .font(.caption2.bold()) + } + .buttonStyle(.borderedProminent) + .tint(.orange) + .controlSize(.mini) + .allowsHitTesting(false) } } #endif @@ -177,7 +178,7 @@ struct ActiveNotificationCard: View { .foregroundStyle(Color.red) .padding(.horizontal, 32) .padding(.vertical, 10) - .background(.white) + .background(Color(.systemBackground)) .clipShape(RoundedRectangle(cornerRadius: 12)) Spacer() } @@ -245,7 +246,7 @@ struct ResolvedNotificationCard: View { } } .padding(16) - .background(.white) + .background(Color(.secondarySystemGroupedBackground)) .clipShape(RoundedRectangle(cornerRadius: 20)) .shadow(color: .black.opacity(0.06), radius: 8, y: 2) } diff --git a/MaydayLiveActivity/MaydayLiveActivityLiveActivity.swift b/MaydayLiveActivity/MaydayLiveActivityLiveActivity.swift index 1f2138a..8b005e7 100644 --- a/MaydayLiveActivity/MaydayLiveActivityLiveActivity.swift +++ b/MaydayLiveActivity/MaydayLiveActivityLiveActivity.swift @@ -1,153 +1,218 @@ import ActivityKit -import WidgetKit import SwiftUI +import WidgetKit struct MaydayLiveActivityLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: AlertAttributes.self) { context in - lockScreenView(context: context) - .activityBackgroundTint(severityColor(context.attributes.severity).opacity(0.12)) - .activitySystemActionForegroundColor(.primary) + // MARK: - Lock Screen / Banner / StandBy + + HStack(spacing: 12) { + RoundedRectangle(cornerRadius: 4) + .fill(severityColor(context.attributes.severity)) + .frame(width: 4) + + VStack(alignment: .leading, spacing: 6) { + HStack(alignment: .firstTextBaseline) { + Text(context.attributes.topic) + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + + Spacer() + + Text(context.state.startedAt, style: .relative) + .font(.caption2) + .monospacedDigit() + .foregroundStyle(.tertiary) + .contentTransition(.numericText(countsDown: false)) + } + + Text(context.state.title) + .font(.subheadline) + .fontWeight(.semibold) + .lineLimit(2) + + HStack(alignment: .firstTextBaseline) { + if let value = context.state.value { + Text(value) + .font(.footnote) + .fontWeight(.medium) + .foregroundStyle(severityColor(context.attributes.severity)) + .contentTransition(.numericText()) + } + + Spacer() + + statusLabel(context.state.status) + } + } + } + .padding(14) + .activityBackgroundTint(severityColor(context.attributes.severity).opacity(0.12)) + .activitySystemActionForegroundColor(severityColor(context.attributes.severity)) + .accessibilityElement(children: .combine) + .accessibilityLabel("\(context.attributes.severity.rawValue) alert: \(context.state.title)") + } dynamicIsland: { context in DynamicIsland { + // MARK: - Expanded + DynamicIslandExpandedRegion(.leading) { - HStack(spacing: 6) { + HStack(spacing: 5) { Image(systemName: severityIcon(context.attributes.severity)) - .font(.caption) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundStyle(severityColor(context.attributes.severity)) + .fixedSize() + Text(context.attributes.topic) - .font(.caption.bold()) + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.white.opacity(0.7)) + .lineLimit(1) + .truncationMode(.tail) + .frame(maxWidth: 90, alignment: .leading) } - .foregroundStyle(severityColor(context.attributes.severity)) + .padding(.leading, 4) } + DynamicIslandExpandedRegion(.trailing) { - statusBadge(context.state.status) + Text(context.state.startedAt, style: .relative) + .font(.caption) + .fontWeight(.medium) + .monospacedDigit() + .foregroundStyle(.white.opacity(0.6)) + .contentTransition(.numericText(countsDown: false)) + .lineLimit(1) + .multilineTextAlignment(.trailing) + .padding(.trailing, 4) } - DynamicIslandExpandedRegion(.bottom) { - VStack(spacing: 8) { + + DynamicIslandExpandedRegion(.bottom, priority: 1) { + VStack(alignment: .leading, spacing: 8) { + Text(context.state.title) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundStyle(.white) + .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) + HStack { - VStack(alignment: .leading, spacing: 3) { - Text(context.state.title) - .font(.subheadline.bold()) - if let value = context.state.value { - Text(value) - .font(.caption) - .foregroundStyle(severityColor(context.attributes.severity)) - } + if let value = context.state.value { + Text(value) + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(severityColor(context.attributes.severity)) + .padding(.horizontal, 10) + .padding(.vertical, 4) + .background( + severityColor(context.attributes.severity).opacity(0.2), + in: ContainerRelativeShape() + ) + .contentTransition(.numericText()) } + Spacer() - VStack(alignment: .trailing, spacing: 3) { - HStack(spacing: 3) { - Image(systemName: "clock") - .font(.caption2) - Text(context.state.startedAt, style: .timer) - .font(.caption.monospacedDigit()) - } - .foregroundStyle(.secondary) - Text(context.state.startedAt.formatted(date: .omitted, time: .shortened)) - .font(.caption2) - .foregroundStyle(.tertiary) - } + + statusLabel(context.state.status) } } .padding(.horizontal, 4) + .padding(.bottom, 8) } + } compactLeading: { - Image(systemName: severityIcon(context.attributes.severity)) - .foregroundStyle(severityColor(context.attributes.severity)) + // MARK: - Compact + HStack(spacing: 4) { + Image(systemName: severityIcon(context.attributes.severity)) + .font(.caption) + .fontWeight(.bold) + .foregroundStyle(severityColor(context.attributes.severity)) + .fixedSize() + + Text(context.attributes.topic) + .font(.caption2) + .fontWeight(.medium) + .foregroundStyle(.white.opacity(0.7)) + .lineLimit(1) + .truncationMode(.tail) + .frame(maxWidth: 70, alignment: .leading) + } + .padding(.leading, 4) + } compactTrailing: { - let shortTopic = context.attributes.topic.components(separatedBy: "/").last ?? context.attributes.topic - let valueText = context.state.value.map { " · \($0)" } ?? "" - Text("\(shortTopic)\(valueText)") + Text(context.state.startedAt, style: .timer) .font(.caption2) - .lineLimit(1) + .fontWeight(.medium) + .monospacedDigit() + .foregroundStyle(.white.opacity(0.8)) + .contentTransition(.numericText(countsDown: false)) + .frame(maxWidth: 40, alignment: .trailing) + .padding(.trailing, 4) + } minimal: { Image(systemName: severityIcon(context.attributes.severity)) + .font(.caption2) + .fontWeight(.bold) .foregroundStyle(severityColor(context.attributes.severity)) } .keylineTint(severityColor(context.attributes.severity)) } } +} - @ViewBuilder - func lockScreenView(context: ActivityViewContext) -> some View { - VStack(spacing: 12) { - HStack(spacing: 12) { - ZStack { - Circle() - .fill(severityColor(context.attributes.severity).opacity(0.15)) - .frame(width: 44, height: 44) - Image(systemName: severityIcon(context.attributes.severity)) - .font(.title3) - .foregroundStyle(severityColor(context.attributes.severity)) - } +// MARK: - Helpers - VStack(alignment: .leading, spacing: 3) { - Text(context.state.title) - .font(.subheadline.bold()) - Text(context.attributes.topic) - .font(.caption) - .foregroundStyle(.secondary) - } - - Spacer() - - statusBadge(context.state.status) - } - - HStack(spacing: 16) { - if let value = context.state.value { - HStack(spacing: 4) { - Circle() - .fill(severityColor(context.attributes.severity)) - .frame(width: 6, height: 6) - Text(value) - .font(.caption.bold()) - .foregroundStyle(severityColor(context.attributes.severity)) - } - } - - Spacer() - - HStack(spacing: 4) { - Image(systemName: "clock") - .font(.caption2) - Text(context.state.startedAt, style: .relative) - .font(.caption) - } - .foregroundStyle(.secondary) - } - } - .padding(16) - } - - @ViewBuilder - func statusBadge(_ status: AlertStatus) -> some View { - let (text, color): (String, Color) = status == .active - ? (String(localized: "alert_status_active"), .red) - : (String(localized: "alert_status_resolved"), .green) - Text(text) - .font(.caption2.bold()) - .textCase(.uppercase) - .padding(.horizontal, 8) - .padding(.vertical, 3) - .background(color.opacity(0.15)) - .foregroundStyle(color) - .clipShape(Capsule()) - } - - func severityColor(_ severity: Severity) -> Color { - switch severity { - case .critical: return .red - case .warning: return .orange - case .info: return .blue - } - } - - func severityIcon(_ severity: Severity) -> String { - switch severity { - case .critical: return "flame.fill" - case .warning: return "exclamationmark.triangle.fill" - case .info: return "info.circle.fill" - } +private func severityColor(_ severity: Severity) -> Color { + switch severity { + case .critical: .red + case .warning: .orange + case .info: .cyan } } + +private func severityIcon(_ severity: Severity) -> String { + switch severity { + case .critical: "exclamationmark.triangle.fill" + case .warning: "exclamationmark.circle.fill" + case .info: "info.circle.fill" + } +} + +@ViewBuilder +private func statusLabel(_ status: AlertStatus) -> some View { + HStack(spacing: 4) { + Circle() + .fill(status == .active ? Color.red : Color.green) + .frame(width: 5, height: 5) + + Text(status == .active ? "Active" : "Resolved") + .font(.caption2) + .fontWeight(.semibold) + .foregroundStyle(status == .active ? .red : .green) + } +} + +#Preview("Live Activity", as: .content, using: AlertAttributes( + topic: "server-health", + alertId: "alert-001", + severity: .critical +)) { + MaydayLiveActivityLiveActivity() +} contentStates: { + AlertAttributes.ContentState( + title: "CPU Usage Exceeded 95%", + value: "Current: 97.3%", + status: .active, + startedAt: .now.addingTimeInterval(-300), + updatedAt: .now + ) + AlertAttributes.ContentState( + title: "CPU Usage Normalized", + value: "Current: 42.1%", + status: .resolved, + startedAt: .now.addingTimeInterval(-600), + updatedAt: .now + ) +}