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
+ )
+}