refactor: add color assets and update UI components
This commit is contained in:
@@ -32,19 +32,19 @@
|
|||||||
AA000001000023 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000023 /* ChangePasswordView.swift */; };
|
AA000001000023 /* ChangePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000023 /* ChangePasswordView.swift */; };
|
||||||
AA000001000024 /* SessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000024 /* SessionsView.swift */; };
|
AA000001000024 /* SessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000024 /* SessionsView.swift */; };
|
||||||
AA000001000025 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000025 /* Assets.xcassets */; };
|
AA000001000025 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000025 /* Assets.xcassets */; };
|
||||||
|
AA000001000027 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000027 /* Localizable.xcstrings */; };
|
||||||
|
AA000001000028 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000028 /* InfoPlist.xcstrings */; };
|
||||||
|
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 */; };
|
||||||
AA000001000034 /* AppTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000034 /* AppTextField.swift */; };
|
AA000001000034 /* AppTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000034 /* AppTextField.swift */; };
|
||||||
AA000001000035 /* AppSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000035 /* AppSecureField.swift */; };
|
AA000001000035 /* AppSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000035 /* AppSecureField.swift */; };
|
||||||
AA000001000036 /* AppBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000036 /* AppBackground.swift */; };
|
AA000001000036 /* AppBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000036 /* AppBackground.swift */; };
|
||||||
AA000001000037 /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000037 /* CardContainer.swift */; };
|
AA000001000037 /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000037 /* CardContainer.swift */; };
|
||||||
AA000001000038 /* NotificationIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000038 /* NotificationIconView.swift */; };
|
AA000001000038 /* NotificationIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000038 /* NotificationIconView.swift */; };
|
||||||
AA000001000039 /* OTPDigitField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000039 /* OTPDigitField.swift */; };
|
AA000001000039 /* OTPDigitField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000039 /* OTPDigitField.swift */; };
|
||||||
AA000001000027 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000027 /* Localizable.xcstrings */; };
|
|
||||||
AA000001000028 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000028 /* InfoPlist.xcstrings */; };
|
|
||||||
AA000001000040 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000040 /* LaunchScreen.storyboard */; };
|
AA000001000040 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000040 /* LaunchScreen.storyboard */; };
|
||||||
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, ); }; };
|
AA000007000001 /* MaydayLiveActivity.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AA000008000001 /* MaydayLiveActivity.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -102,16 +102,16 @@
|
|||||||
AA000002000027 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
AA000002000027 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||||
AA000002000028 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
|
AA000002000028 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
|
||||||
AA000002000029 /* Mayday.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mayday.entitlements; sourceTree = "<group>"; };
|
AA000002000029 /* Mayday.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mayday.entitlements; sourceTree = "<group>"; };
|
||||||
AA000002000040 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
|
||||||
AA000002000030 /* MaydayLiveActivityBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityBundle.swift; sourceTree = "<group>"; };
|
AA000002000030 /* MaydayLiveActivityBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityBundle.swift; sourceTree = "<group>"; };
|
||||||
AA000002000031 /* MaydayLiveActivityLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityLiveActivity.swift; sourceTree = "<group>"; };
|
AA000002000031 /* MaydayLiveActivityLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityLiveActivity.swift; sourceTree = "<group>"; };
|
||||||
|
AA000002000033 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
AA000002000034 /* AppTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTextField.swift; sourceTree = "<group>"; };
|
AA000002000034 /* AppTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTextField.swift; sourceTree = "<group>"; };
|
||||||
AA000002000035 /* AppSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecureField.swift; sourceTree = "<group>"; };
|
AA000002000035 /* AppSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSecureField.swift; sourceTree = "<group>"; };
|
||||||
AA000002000036 /* AppBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackground.swift; sourceTree = "<group>"; };
|
AA000002000036 /* AppBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackground.swift; sourceTree = "<group>"; };
|
||||||
AA000002000037 /* CardContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardContainer.swift; sourceTree = "<group>"; };
|
AA000002000037 /* CardContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardContainer.swift; sourceTree = "<group>"; };
|
||||||
AA000002000038 /* NotificationIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationIconView.swift; sourceTree = "<group>"; };
|
AA000002000038 /* NotificationIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationIconView.swift; sourceTree = "<group>"; };
|
||||||
AA000002000039 /* OTPDigitField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPDigitField.swift; sourceTree = "<group>"; };
|
AA000002000039 /* OTPDigitField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPDigitField.swift; sourceTree = "<group>"; };
|
||||||
AA000002000033 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
AA000002000040 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
AA000008000001 /* MaydayLiveActivity.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MaydayLiveActivity.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
AA000008000001 /* MaydayLiveActivity.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MaydayLiveActivity.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
AA000009000001 /* Mayday.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mayday.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
AA000009000001 /* Mayday.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mayday.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
@@ -237,6 +237,16 @@
|
|||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
AA000011000010 /* MaydayLiveActivity */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
AA000002000030 /* MaydayLiveActivityBundle.swift */,
|
||||||
|
AA000002000031 /* MaydayLiveActivityLiveActivity.swift */,
|
||||||
|
AA000002000033 /* Info.plist */,
|
||||||
|
);
|
||||||
|
path = MaydayLiveActivity;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
AA000011000011 /* UIKit */ = {
|
AA000011000011 /* UIKit */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -250,16 +260,6 @@
|
|||||||
path = UIKit;
|
path = UIKit;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
AA000011000010 /* MaydayLiveActivity */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
AA000002000030 /* MaydayLiveActivityBundle.swift */,
|
|
||||||
AA000002000031 /* MaydayLiveActivityLiveActivity.swift */,
|
|
||||||
AA000002000033 /* Info.plist */,
|
|
||||||
);
|
|
||||||
path = MaydayLiveActivity;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
AA000011000099 /* Products */ = {
|
AA000011000099 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -449,6 +449,9 @@
|
|||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.robonen.mayday;
|
PRODUCT_BUNDLE_IDENTIFIER = com.robonen.mayday;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@@ -477,6 +480,9 @@
|
|||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.robonen.mayday;
|
PRODUCT_BUNDLE_IDENTIFIER = com.robonen.mayday;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
{
|
{
|
||||||
"colors" : [
|
"colors" : [
|
||||||
{
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.188",
|
||||||
|
"green" : "0.231",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.227",
|
||||||
|
"green" : "0.271",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.188",
|
||||||
|
"green" : "0.231",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.227",
|
||||||
|
"green" : "0.271",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "0.478",
|
||||||
|
"red" : "0.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "0.518",
|
||||||
|
"red" : "0.039"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.349",
|
||||||
|
"green" : "0.780",
|
||||||
|
"red" : "0.204"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.345",
|
||||||
|
"green" : "0.820",
|
||||||
|
"red" : "0.188"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.000",
|
||||||
|
"green" : "0.584",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.039",
|
||||||
|
"green" : "0.624",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@EnvironmentObject var authViewModel: AuthViewModel
|
@Environment(AuthViewModel.self) private var authViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
@@ -19,3 +19,8 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentView()
|
||||||
|
.environment(AuthViewModel())
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,23 @@
|
|||||||
"sourceLanguage" : "ru",
|
"sourceLanguage" : "ru",
|
||||||
"strings" : {
|
"strings" : {
|
||||||
"(%lld)" : {
|
"(%lld)" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "(%lld)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"%@ alert: %@" : {
|
"%@ alert: %@" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "%1$@ alert: %2$@"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ru" : {
|
"ru" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "new",
|
"state" : "new",
|
||||||
@@ -31,7 +44,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Active" : {
|
"Active" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Active"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"active_sessions" : {
|
"active_sessions" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -65,40 +85,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alert_status_active" : {
|
|
||||||
"extractionState" : "stale",
|
|
||||||
"localizations" : {
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"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" : {
|
"cancel" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@@ -292,7 +278,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Email" : {
|
"Email" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Email"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"error_invalid_credentials" : {
|
"error_invalid_credentials" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -503,7 +496,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Mayday" : {
|
"Mayday" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Mayday"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"new_password" : {
|
"new_password" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -618,7 +618,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"OK" : {
|
"OK" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"open_button" : {
|
"open_button" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -797,7 +804,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Resolved" : {
|
"Resolved" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resolved"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"save_button" : {
|
"save_button" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import SwiftUI
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct MaydayApp: App {
|
struct MaydayApp: App {
|
||||||
@StateObject private var authViewModel = AuthViewModel()
|
@State private var authViewModel = AuthViewModel()
|
||||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
.environmentObject(authViewModel)
|
.environment(authViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@Observable
|
||||||
@MainActor
|
@MainActor
|
||||||
class AuthViewModel: ObservableObject {
|
final class AuthViewModel {
|
||||||
@Published var isAuthenticated = false
|
var isAuthenticated = false
|
||||||
@Published var isCheckingAuth = true
|
var isCheckingAuth = true
|
||||||
@Published var currentUser: UserResponse?
|
var currentUser: UserResponse?
|
||||||
@Published var isLoading = false
|
var isLoading = false
|
||||||
@Published var error: String?
|
var error: String?
|
||||||
|
|
||||||
private let auth = AuthService.shared
|
private let auth = AuthService.shared
|
||||||
private let keychain = KeychainService.shared
|
private let keychain = KeychainService.shared
|
||||||
|
|||||||
@@ -2,16 +2,25 @@ import Foundation
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
@Observable
|
||||||
@MainActor
|
@MainActor
|
||||||
class NotificationsViewModel: ObservableObject {
|
final class NotificationsViewModel {
|
||||||
@Published var notifications: [AppNotification] = []
|
var notifications: [AppNotification] = []
|
||||||
@Published var unreadCount = 0
|
var unreadCount = 0
|
||||||
@Published var isLoading = false
|
var isLoading = false
|
||||||
@Published var isLoadingMore = false
|
var isLoadingMore = false
|
||||||
@Published var error: String?
|
var error: String?
|
||||||
@Published var hasMore = true
|
var hasMore = true
|
||||||
private var hasLoadedOnce = false
|
private var hasLoadedOnce = false
|
||||||
|
|
||||||
|
var unreadNotifications: [AppNotification] {
|
||||||
|
notifications.filter { !$0.isRead }
|
||||||
|
}
|
||||||
|
|
||||||
|
var readNotifications: [AppNotification] {
|
||||||
|
notifications.filter { $0.isRead }
|
||||||
|
}
|
||||||
|
|
||||||
private let service = NotificationsAPIService.shared
|
private let service = NotificationsAPIService.shared
|
||||||
private let limit = 50
|
private let limit = 50
|
||||||
private var currentOffset = 0
|
private var currentOffset = 0
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@Observable
|
||||||
@MainActor
|
@MainActor
|
||||||
class SettingsViewModel: ObservableObject {
|
final class SettingsViewModel {
|
||||||
@Published var sessions: [SessionResponse] = []
|
var sessions: [SessionResponse] = []
|
||||||
@Published var isLoading = false
|
var isLoading = false
|
||||||
@Published var error: String?
|
var error: String?
|
||||||
@Published var successMessage: String?
|
var successMessage: String?
|
||||||
|
|
||||||
private let service = NotificationsAPIService.shared
|
private let service = NotificationsAPIService.shared
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
@EnvironmentObject var authViewModel: AuthViewModel
|
@Environment(AuthViewModel.self) private var authViewModel
|
||||||
@State private var email = ""
|
@State private var email = ""
|
||||||
@State private var password = ""
|
@State private var password = ""
|
||||||
@State private var showRegister = false
|
@State private var showRegister = false
|
||||||
@@ -23,7 +23,7 @@ struct LoginView: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 84, height: 84)
|
.frame(width: 84, height: 84)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
|
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
|
||||||
.shadow(color: .red.opacity(0.25), radius: 12, y: 6)
|
.shadow(color: .brand.opacity(0.25), radius: 12, y: 6)
|
||||||
|
|
||||||
Text("Mayday")
|
Text("Mayday")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
@@ -56,7 +56,7 @@ struct LoginView: View {
|
|||||||
|
|
||||||
if let error = authViewModel.error {
|
if let error = authViewModel.error {
|
||||||
Text(error)
|
Text(error)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.brand)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@@ -69,7 +69,7 @@ struct LoginView: View {
|
|||||||
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [Color.red, Color.red.opacity(0.82)],
|
colors: [Color.brand, Color.brand.opacity(0.82)],
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
)
|
)
|
||||||
@@ -107,3 +107,8 @@ struct LoginView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LoginView()
|
||||||
|
.environment(AuthViewModel())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct RegisterView: View {
|
struct RegisterView: View {
|
||||||
@EnvironmentObject var authViewModel: AuthViewModel
|
@Environment(AuthViewModel.self) private var authViewModel
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@State private var email = ""
|
@State private var email = ""
|
||||||
@State private var password = ""
|
@State private var password = ""
|
||||||
@@ -26,7 +26,7 @@ struct RegisterView: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 76, height: 76)
|
.frame(width: 76, height: 76)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
|
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
|
||||||
.shadow(color: .red.opacity(0.22), radius: 12, y: 6)
|
.shadow(color: .brand.opacity(0.22), radius: 12, y: 6)
|
||||||
|
|
||||||
Text("register_title")
|
Text("register_title")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
@@ -48,21 +48,21 @@ struct RegisterView: View {
|
|||||||
|
|
||||||
if password.count > 0 && password.count < 8 {
|
if password.count > 0 && password.count < 8 {
|
||||||
Text("password_min_length")
|
Text("password_min_length")
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.brand)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
if confirmPassword.count > 0 && password != confirmPassword {
|
if confirmPassword.count > 0 && password != confirmPassword {
|
||||||
Text("passwords_mismatch")
|
Text("passwords_mismatch")
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.brand)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let error = authViewModel.error {
|
if let error = authViewModel.error {
|
||||||
Text(error)
|
Text(error)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.brand)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@@ -81,7 +81,7 @@ struct RegisterView: View {
|
|||||||
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [Color.red, Color.red.opacity(0.82)],
|
colors: [Color.brand, Color.brand.opacity(0.82)],
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
)
|
)
|
||||||
@@ -122,3 +122,10 @@ struct RegisterView: View {
|
|||||||
!email.isEmpty && password.count >= 8 && password == confirmPassword
|
!email.isEmpty && password.count >= 8 && password == confirmPassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NavigationStack {
|
||||||
|
RegisterView()
|
||||||
|
}
|
||||||
|
.environment(AuthViewModel())
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ struct VerifyEmailView: View {
|
|||||||
let email: String
|
let email: String
|
||||||
let password: String
|
let password: String
|
||||||
|
|
||||||
@EnvironmentObject var authViewModel: AuthViewModel
|
@Environment(AuthViewModel.self) private var authViewModel
|
||||||
@State private var codeDigits: [String] = Array(repeating: "", count: 6)
|
@State private var codeDigits: [String] = Array(repeating: "", count: 6)
|
||||||
@State private var resendCooldown = 0
|
@State private var resendCooldown = 0
|
||||||
@State private var focusedIndex: Int?
|
@State private var focusedIndex: Int?
|
||||||
@@ -40,7 +40,7 @@ struct VerifyEmailView: View {
|
|||||||
|
|
||||||
if let error = authViewModel.error {
|
if let error = authViewModel.error {
|
||||||
Text(error)
|
Text(error)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.brand)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@@ -65,7 +65,7 @@ struct VerifyEmailView: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 72, height: 72)
|
.frame(width: 72, height: 72)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||||
.shadow(color: .red.opacity(0.22), radius: 12, y: 6)
|
.shadow(color: .brand.opacity(0.22), radius: 12, y: 6)
|
||||||
|
|
||||||
Text("verify_email_title")
|
Text("verify_email_title")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
@@ -102,7 +102,7 @@ struct VerifyEmailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.footnote.weight(.semibold))
|
.font(.footnote.weight(.semibold))
|
||||||
.foregroundStyle(resendCooldown > 0 ? Color.secondary : Color.red)
|
.foregroundStyle(resendCooldown > 0 ? Color.secondary : Color.brand)
|
||||||
.padding(.horizontal, 14)
|
.padding(.horizontal, 14)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
.background(Color(.tertiarySystemFill))
|
.background(Color(.tertiarySystemFill))
|
||||||
@@ -136,7 +136,7 @@ struct VerifyEmailView: View {
|
|||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
.stroke(
|
.stroke(
|
||||||
focusedIndex == index
|
focusedIndex == index
|
||||||
? Color.red.opacity(0.9)
|
? Color.brand.opacity(0.9)
|
||||||
: Color.primary.opacity(0.10),
|
: Color.primary.opacity(0.10),
|
||||||
lineWidth: focusedIndex == index ? 2 : 1
|
lineWidth: focusedIndex == index ? 2 : 1
|
||||||
)
|
)
|
||||||
@@ -188,3 +188,10 @@ struct VerifyEmailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NavigationStack {
|
||||||
|
VerifyEmailView(email: "user@example.com", password: "password123")
|
||||||
|
}
|
||||||
|
.environment(AuthViewModel())
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct NotificationDetailView: View {
|
struct NotificationDetailView: View {
|
||||||
let notificationId: UUID
|
let notificationId: UUID
|
||||||
@ObservedObject var viewModel: NotificationsViewModel
|
var viewModel: NotificationsViewModel
|
||||||
|
|
||||||
private var notification: AppNotification? {
|
private var notification: AppNotification? {
|
||||||
viewModel.notifications.first { $0.id == notificationId }
|
viewModel.notifications.first { $0.id == notificationId }
|
||||||
@@ -56,12 +56,12 @@ struct NotificationDetailView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Text("mark_as_read")
|
Text("mark_as_read")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.brand)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, 14)
|
.padding(.vertical, 14)
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 16)
|
RoundedRectangle(cornerRadius: 16)
|
||||||
.fill(Color.red.opacity(0.1))
|
.fill(Color.brand.opacity(0.1))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
@@ -109,8 +109,8 @@ struct NotificationDetailView: View {
|
|||||||
|
|
||||||
private func statusBadge(for notification: AppNotification) -> some View {
|
private func statusBadge(for notification: AppNotification) -> some View {
|
||||||
let (text, color): (String, Color) = notification.isRead
|
let (text, color): (String, Color) = notification.isRead
|
||||||
? (String(localized: "status_read"), .green)
|
? (String(localized: "status_read"), .success)
|
||||||
: (String(localized: "status_new"), .red)
|
: (String(localized: "status_new"), .brand)
|
||||||
return Text(text)
|
return Text(text)
|
||||||
.font(.caption.bold())
|
.font(.caption.bold())
|
||||||
.foregroundStyle(color)
|
.foregroundStyle(color)
|
||||||
@@ -239,3 +239,18 @@ struct NotificationDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
let notification = AppNotification(
|
||||||
|
id: UUID(), userId: UUID(), scopeId: nil, channel: .inApp,
|
||||||
|
contentType: .plain, templateId: nil, subject: "CPU Usage Critical",
|
||||||
|
body: "Server load has exceeded 95% for the last 5 minutes. Immediate action is required to prevent service degradation.",
|
||||||
|
source: "monitoring", metadata: ["severity": "critical", "host": "prod-01", "region": "eu-west-1"],
|
||||||
|
status: .sent, error: nil, attempts: 1, maxAttempts: 3,
|
||||||
|
nextRetryAt: nil, sentAt: Date(), readAt: nil, createdAt: Date()
|
||||||
|
)
|
||||||
|
let vm = NotificationsViewModel()
|
||||||
|
NavigationStack {
|
||||||
|
NotificationDetailView(notification: notification, viewModel: vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationsView: View {
|
struct NotificationsView: View {
|
||||||
@EnvironmentObject var authViewModel: AuthViewModel
|
@Environment(AuthViewModel.self) private var authViewModel
|
||||||
@StateObject private var viewModel = NotificationsViewModel()
|
@State private var viewModel = NotificationsViewModel()
|
||||||
@State private var showSettings = false
|
@State private var showSettings = false
|
||||||
|
|
||||||
private var unreadNotifications: [AppNotification] {
|
|
||||||
viewModel.notifications.filter { !$0.isRead }
|
|
||||||
}
|
|
||||||
|
|
||||||
private var readNotifications: [AppNotification] {
|
|
||||||
viewModel.notifications.filter { $0.isRead }
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Group {
|
Group {
|
||||||
@@ -49,7 +41,7 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $showSettings) {
|
.sheet(isPresented: $showSettings) {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.environmentObject(authViewModel)
|
.environment(authViewModel)
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
await viewModel.load()
|
await viewModel.load()
|
||||||
@@ -64,12 +56,12 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var notificationsList: some View {
|
private var notificationsList: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
if !unreadNotifications.isEmpty {
|
if !viewModel.unreadNotifications.isEmpty {
|
||||||
sectionHeader(String(localized: "notifications_active"))
|
sectionHeader(String(localized: "notifications_active"))
|
||||||
ForEach(unreadNotifications) { notification in
|
ForEach(viewModel.unreadNotifications) { notification in
|
||||||
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
||||||
ActiveNotificationCard(notification: notification)
|
ActiveNotificationCard(notification: notification)
|
||||||
}
|
}
|
||||||
@@ -85,9 +77,9 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !readNotifications.isEmpty {
|
if !viewModel.readNotifications.isEmpty {
|
||||||
sectionHeader(String(localized: "notifications_completed"))
|
sectionHeader(String(localized: "notifications_completed"))
|
||||||
ForEach(readNotifications) { notification in
|
ForEach(viewModel.readNotifications) { notification in
|
||||||
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
||||||
ResolvedNotificationCard(notification: notification)
|
ResolvedNotificationCard(notification: notification)
|
||||||
}
|
}
|
||||||
@@ -112,7 +104,7 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sectionHeader(_ title: String) -> some View {
|
private func sectionHeader(_ title: String) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
@@ -127,6 +119,43 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
private extension AppNotification {
|
||||||
|
static let previewUnread = AppNotification(
|
||||||
|
id: UUID(), userId: UUID(), scopeId: nil, channel: .inApp,
|
||||||
|
contentType: .plain, templateId: nil, subject: "CPU Usage Critical",
|
||||||
|
body: "Server load has exceeded 95% for the last 5 minutes.",
|
||||||
|
source: "monitoring", metadata: ["severity": "critical"],
|
||||||
|
status: .sent, error: nil, attempts: 1, maxAttempts: 3,
|
||||||
|
nextRetryAt: nil, sentAt: Date(), readAt: nil, createdAt: Date()
|
||||||
|
)
|
||||||
|
|
||||||
|
static let previewRead = AppNotification(
|
||||||
|
id: UUID(), userId: UUID(), scopeId: nil, channel: .inApp,
|
||||||
|
contentType: .plain, templateId: nil, subject: "Deployment Successful",
|
||||||
|
body: "Version 2.1.0 has been deployed to production.",
|
||||||
|
source: "ci/cd", metadata: ["severity": "success"],
|
||||||
|
status: .read, error: nil, attempts: 1, maxAttempts: 3,
|
||||||
|
nextRetryAt: nil, sentAt: Date(), readAt: Date(), createdAt: Date().addingTimeInterval(-3600)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Active Card") {
|
||||||
|
ActiveNotificationCard(notification: .previewUnread)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Resolved Card") {
|
||||||
|
ResolvedNotificationCard(notification: .previewRead)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Notifications List") {
|
||||||
|
NotificationsView()
|
||||||
|
.environment(AuthViewModel())
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Active (Unread) Card
|
// MARK: - Active (Unread) Card
|
||||||
|
|
||||||
struct ActiveNotificationCard: View {
|
struct ActiveNotificationCard: View {
|
||||||
@@ -166,7 +195,7 @@ struct ActiveNotificationCard: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Text("open_button")
|
Text("open_button")
|
||||||
.font(.subheadline.bold())
|
.font(.subheadline.bold())
|
||||||
.foregroundStyle(Color.red)
|
.foregroundStyle(Color.brand)
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
.background(Color(.systemBackground))
|
.background(Color(.systemBackground))
|
||||||
@@ -177,13 +206,13 @@ struct ActiveNotificationCard: View {
|
|||||||
.padding(16)
|
.padding(16)
|
||||||
.background(
|
.background(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [Color.red, Color.red.opacity(0.85)],
|
colors: [Color.brand, Color.brand.opacity(0.85)],
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 20))
|
.clipShape(RoundedRectangle(cornerRadius: 20))
|
||||||
.shadow(color: .red.opacity(0.3), radius: 8, y: 4)
|
.shadow(color: .brand.opacity(0.3), radius: 8, y: 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +260,7 @@ struct ResolvedNotificationCard: View {
|
|||||||
HStack(spacing: 4) {
|
HStack(spacing: 4) {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.success)
|
||||||
Text("notification_read_at \(readAt.formatted(date: .abbreviated, time: .shortened))")
|
Text("notification_read_at \(readAt.formatted(date: .abbreviated, time: .shortened))")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|||||||
@@ -1,46 +1,98 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ChangePasswordView: View {
|
struct ChangePasswordView: View {
|
||||||
@StateObject private var viewModel = SettingsViewModel()
|
var viewModel: SettingsViewModel
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@State private var currentPassword = ""
|
@State private var currentPassword = ""
|
||||||
@State private var newPassword = ""
|
@State private var newPassword = ""
|
||||||
@State private var confirmPassword = ""
|
@State private var confirmPassword = ""
|
||||||
|
|
||||||
|
private var isFormInvalid: Bool {
|
||||||
|
!isFormValid || viewModel.isLoading
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isFormValid: Bool {
|
||||||
|
!currentPassword.isEmpty && newPassword.count >= 8 && newPassword == confirmPassword
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
SecureField("current_password", text: $currentPassword)
|
VStack(spacing: 12) {
|
||||||
|
AppSecureField(
|
||||||
|
title: "current_password",
|
||||||
|
icon: "lock.fill",
|
||||||
|
text: $currentPassword
|
||||||
|
)
|
||||||
.textContentType(.password)
|
.textContentType(.password)
|
||||||
SecureField("new_password", text: $newPassword)
|
|
||||||
|
AppSecureField(
|
||||||
|
title: "new_password",
|
||||||
|
icon: "key.fill",
|
||||||
|
text: $newPassword
|
||||||
|
)
|
||||||
.textContentType(.newPassword)
|
.textContentType(.newPassword)
|
||||||
SecureField("confirm_new_password", text: $confirmPassword)
|
|
||||||
|
AppSecureField(
|
||||||
|
title: "confirm_new_password",
|
||||||
|
icon: "lock.rotation",
|
||||||
|
text: $confirmPassword
|
||||||
|
)
|
||||||
.textContentType(.newPassword)
|
.textContentType(.newPassword)
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
if newPassword.count > 0 && newPassword.count < 8 {
|
||||||
|
Section {
|
||||||
|
Text("password_min_length")
|
||||||
|
.foregroundStyle(.brand)
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirmPassword.count > 0 && newPassword != confirmPassword {
|
||||||
|
Section {
|
||||||
|
Text("passwords_mismatch")
|
||||||
|
.foregroundStyle(.brand)
|
||||||
|
.font(.footnote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let error = viewModel.error {
|
if let error = viewModel.error {
|
||||||
Section {
|
Section {
|
||||||
Text(error).foregroundStyle(.red)
|
Text(error)
|
||||||
|
.foregroundStyle(.brand)
|
||||||
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let success = viewModel.successMessage {
|
if let success = viewModel.successMessage {
|
||||||
Section {
|
Section {
|
||||||
Text(success).foregroundStyle(.green)
|
Text(success)
|
||||||
|
.foregroundStyle(.success)
|
||||||
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button("save_button") {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
let success = await viewModel.changePassword(current: currentPassword, new: newPassword)
|
let success = await viewModel.changePassword(current: currentPassword, new: newPassword)
|
||||||
if success { dismiss() }
|
if success { dismiss() }
|
||||||
}
|
}
|
||||||
}
|
} label: {
|
||||||
.disabled(!isFormValid || viewModel.isLoading)
|
if viewModel.isLoading {
|
||||||
|
ProgressView()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
} else {
|
||||||
|
Text("save_button")
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(isFormInvalid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("change_password_title")
|
.navigationTitle("change_password_title")
|
||||||
@@ -52,8 +104,8 @@ struct ChangePasswordView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var isFormValid: Bool {
|
|
||||||
!currentPassword.isEmpty && newPassword.count >= 8 && newPassword == confirmPassword
|
#Preview {
|
||||||
}
|
ChangePasswordView(viewModel: SettingsViewModel())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SessionsView: View {
|
struct SessionsView: View {
|
||||||
@EnvironmentObject var viewModel: SettingsViewModel
|
var viewModel: SettingsViewModel
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -18,8 +18,8 @@ struct SessionsView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
.padding(.horizontal, 6)
|
.padding(.horizontal, 6)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
.background(Color.green.opacity(0.2))
|
.background(Color.success.opacity(0.2))
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.success)
|
||||||
.cornerRadius(4)
|
.cornerRadius(4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,3 +51,7 @@ struct SessionsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SessionsView(viewModel: SettingsViewModel())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@EnvironmentObject var authViewModel: AuthViewModel
|
@Environment(AuthViewModel.self) private var authViewModel
|
||||||
@StateObject private var viewModel = SettingsViewModel()
|
@State private var viewModel = SettingsViewModel()
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@State private var showChangePassword = false
|
@State private var showChangePassword = false
|
||||||
@State private var showSessions = false
|
@State private var showSessions = false
|
||||||
@State private var showLogoutAllConfirm = false
|
@State private var showLogoutAllConfirm = false
|
||||||
@State private var logoutAllError: String?
|
@State private var logoutAllError: String?
|
||||||
|
@State private var showLogoutAllError = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -19,9 +20,12 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button("change_password") {
|
Button {
|
||||||
showChangePassword = true
|
showChangePassword = true
|
||||||
|
} label: {
|
||||||
|
Text("change_password")
|
||||||
}
|
}
|
||||||
|
.tint(.primary)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if let url = URL(string: UIApplication.openNotificationSettingsURLString) {
|
if let url = URL(string: UIApplication.openNotificationSettingsURLString) {
|
||||||
@@ -29,8 +33,8 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("push_notifications", systemImage: "bell.badge")
|
Label("push_notifications", systemImage: "bell.badge")
|
||||||
.foregroundStyle(.primary)
|
|
||||||
}
|
}
|
||||||
|
.tint(.primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
@@ -48,7 +52,7 @@ struct SettingsView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.primary)
|
.tint(.primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
@@ -71,6 +75,7 @@ struct SettingsView: View {
|
|||||||
await authViewModel.logout()
|
await authViewModel.logout()
|
||||||
} catch {
|
} catch {
|
||||||
logoutAllError = error.localizedDescription
|
logoutAllError = error.localizedDescription
|
||||||
|
showLogoutAllError = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,19 +91,12 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showChangePassword) {
|
.sheet(isPresented: $showChangePassword) {
|
||||||
ChangePasswordView()
|
ChangePasswordView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showSessions) {
|
.sheet(isPresented: $showSessions) {
|
||||||
SessionsView()
|
SessionsView(viewModel: viewModel)
|
||||||
.environmentObject(viewModel)
|
|
||||||
}
|
}
|
||||||
.alert(
|
.alert("error_title", isPresented: $showLogoutAllError) {
|
||||||
"error_title",
|
|
||||||
isPresented: Binding(
|
|
||||||
get: { logoutAllError != nil },
|
|
||||||
set: { if !$0 { logoutAllError = nil } }
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Button("OK") { logoutAllError = nil }
|
Button("OK") { logoutAllError = nil }
|
||||||
} message: {
|
} message: {
|
||||||
Text(logoutAllError ?? "")
|
Text(logoutAllError ?? "")
|
||||||
@@ -109,3 +107,8 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SettingsView()
|
||||||
|
.environment(AuthViewModel())
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ struct AppBackgroundModifier: ViewModifier {
|
|||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content.background(
|
content.background(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [Color(.systemGroupedBackground), Color.red.opacity(0.08)],
|
colors: [Color(.systemGroupedBackground), Color.brand.opacity(0.08)],
|
||||||
startPoint: .top,
|
startPoint: .top,
|
||||||
endPoint: .bottom
|
endPoint: .bottom
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
static let brand = Color("Brand")
|
||||||
|
static let success = Color("Success")
|
||||||
|
static let warning = Color("Warning")
|
||||||
|
static let info = Color("Info")
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ShapeStyle where Self == Color {
|
||||||
|
static var brand: Color { .brand }
|
||||||
|
static var success: Color { .success }
|
||||||
|
static var warning: Color { .warning }
|
||||||
|
static var info: Color { .info }
|
||||||
|
}
|
||||||
@@ -23,3 +23,8 @@ struct AppSecureField: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
AppSecureField(title: "Password", icon: "lock.fill", text: .constant(""))
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,3 +23,8 @@ struct AppTextField: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
AppTextField(title: "Email", icon: "envelope.fill", text: .constant("user@example.com"))
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ enum NotificationSeverity: String {
|
|||||||
|
|
||||||
var color: Color {
|
var color: Color {
|
||||||
switch self {
|
switch self {
|
||||||
case .critical: return .red
|
case .critical: return .brand
|
||||||
case .warning: return .orange
|
case .warning: return .warning
|
||||||
case .info: return .blue
|
case .info: return .info
|
||||||
case .success: return .green
|
case .success: return .success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,3 +45,13 @@ struct NotificationIconView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
NotificationIconView(severity: .critical, isActive: true)
|
||||||
|
NotificationIconView(severity: .warning, isActive: false)
|
||||||
|
NotificationIconView(severity: .info, isActive: false)
|
||||||
|
NotificationIconView(severity: .success, isActive: false)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user