First batch of UI updates
The focus was on neumorphic buttons that work for both light and dark as well as state of the button (pressed)
This commit is contained in:
parent
23126eb97b
commit
81ccc2b283
|
@ -82,6 +82,7 @@
|
|||
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; };
|
||||
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; };
|
||||
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
|
||||
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; };
|
||||
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
|
||||
F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; };
|
||||
F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; };
|
||||
|
@ -209,6 +210,7 @@
|
|||
66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
|
||||
66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = "<group>"; };
|
||||
9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicDesignModifier.swift; sourceTree = "<group>"; };
|
||||
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = "<group>"; };
|
||||
F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = "<group>"; };
|
||||
F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = "<group>"; };
|
||||
|
@ -562,6 +564,7 @@
|
|||
663FAB9F271D876200E495F8 /* PrimaryButton.swift */,
|
||||
663FABA1271D876C00E495F8 /* SecondaryButton.swift */,
|
||||
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */,
|
||||
9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */,
|
||||
);
|
||||
path = Buttons;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1000,6 +1003,7 @@
|
|||
0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */,
|
||||
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */,
|
||||
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */,
|
||||
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */,
|
||||
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
|
||||
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift in Sources */,
|
||||
0D354A0B26D5A9D000315F45 /* MnemonicSeedPhraseHandling.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.330",
|
||||
"blue" : "0x36",
|
||||
"green" : "0x2C",
|
||||
"red" : "0x27"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.000",
|
||||
"blue" : "0x36",
|
||||
"green" : "0x2C",
|
||||
"red" : "0x27"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE2",
|
||||
"green" : "0xDA",
|
||||
"red" : "0xD0"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.150",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "0.150",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.937",
|
||||
"green" : "0.863",
|
||||
"red" : "0.784"
|
||||
"blue" : "0xFC",
|
||||
"green" : "0xF8",
|
||||
"red" : "0xF5"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.937",
|
||||
"green" : "0.863",
|
||||
"red" : "0.784"
|
||||
"blue" : "0xD9",
|
||||
"green" : "0xC0",
|
||||
"red" : "0xA7"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xD9",
|
||||
"green" : "0xC0",
|
||||
"red" : "0xA7"
|
||||
"blue" : "0xFC",
|
||||
"green" : "0xF8",
|
||||
"red" : "0xF5"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xD9",
|
||||
"green" : "0xC0",
|
||||
"red" : "0xA7"
|
||||
"blue" : "0xEF",
|
||||
"green" : "0xDC",
|
||||
"red" : "0xC8"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFC",
|
||||
"green" : "0xF8",
|
||||
"red" : "0xF5"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.514",
|
||||
"green" : "0.486",
|
||||
"red" : "0.443"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -20,12 +20,12 @@
|
|||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.333",
|
||||
"green" : "0.192",
|
||||
"red" : "0.141"
|
||||
"blue" : "0x52",
|
||||
"green" : "0x31",
|
||||
"red" : "0x26"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x59",
|
||||
"green" : "0x35",
|
||||
"red" : "0x28"
|
||||
"blue" : "0x47",
|
||||
"green" : "0x37",
|
||||
"red" : "0x2D"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -44,9 +44,12 @@ internal enum Asset {
|
|||
}
|
||||
internal enum Buttons {
|
||||
internal static let activeButton = ColorAsset(name: "ActiveButton")
|
||||
internal static let buttonsTitleShadow = ColorAsset(name: "ButtonsTitleShadow")
|
||||
internal static let createButton = ColorAsset(name: "CreateButton")
|
||||
internal static let createButtonDisabled = ColorAsset(name: "CreateButtonDisabled")
|
||||
internal static let createButtonPressed = ColorAsset(name: "CreateButtonPressed")
|
||||
internal static let neumorphicDarkSide = ColorAsset(name: "NeumorphicDarkSide")
|
||||
internal static let neumorphicLightSide = ColorAsset(name: "NeumorphicLightSide")
|
||||
internal static let onboardingNavigation = ColorAsset(name: "OnboardingNavigation")
|
||||
internal static let onboardingNavigationPressed = ColorAsset(name: "OnboardingNavigationPressed")
|
||||
internal static let primaryButton = ColorAsset(name: "PrimaryButton")
|
||||
|
@ -56,7 +59,8 @@ internal enum Asset {
|
|||
internal static let secondaryButtonPressed = ColorAsset(name: "SecondaryButtonPressed")
|
||||
}
|
||||
internal enum Onboarding {
|
||||
internal static let circularFrame = ColorAsset(name: "CircularFrame")
|
||||
internal static let circularFrameGradientEnd = ColorAsset(name: "CircularFrameGradientEnd")
|
||||
internal static let circularFrameGradientStart = ColorAsset(name: "CircularFrameGradientStart")
|
||||
internal static let navigationButtonDisabled = ColorAsset(name: "NavigationButtonDisabled")
|
||||
internal static let navigationButtonEnabled = ColorAsset(name: "NavigationButtonEnabled")
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ struct OnboardingContentView: View {
|
|||
}
|
||||
)
|
||||
)
|
||||
.frame(width: width * 0.85, height: width * 0.85)
|
||||
.frame(width: width * 0.82, height: width * 0.82)
|
||||
.badgeIcons(
|
||||
store.actionless.scope(
|
||||
state: { state in
|
||||
|
@ -61,16 +61,15 @@ struct OnboardingContentView: View {
|
|||
VStack(spacing: viewStore.isFinalStep ? 50 : 15) {
|
||||
HStack {
|
||||
Text(viewStore.steps[stepIndex].title)
|
||||
.font(.custom(FontFamily.Roboto.bold.name, size: 30))
|
||||
.fontWeight(.regular)
|
||||
.titleText()
|
||||
if !viewStore.isFinalStep {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
Text(viewStore.steps[stepIndex].description)
|
||||
.font(.custom(FontFamily.Roboto.regular.name, size: 15))
|
||||
.lineSpacing(5)
|
||||
.bodyText()
|
||||
.opacity(0.53)
|
||||
}
|
||||
.opacity(stepIndex == viewStore.index ? 1: 0)
|
||||
.padding(.horizontal, 35)
|
||||
|
@ -85,7 +84,7 @@ struct OnboardingContentView: View {
|
|||
struct OnboardingContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let store = Store(
|
||||
initialState: OnboardingState(index: 2),
|
||||
initialState: OnboardingState(index: 0),
|
||||
reducer: OnboardingReducer.default,
|
||||
environment: ()
|
||||
)
|
||||
|
@ -114,8 +113,9 @@ struct OnboardingContentView_Previews: PreviewProvider {
|
|||
width: proxy.size.width,
|
||||
height: proxy.size.height
|
||||
)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
||||
.applyScreenBackground()
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,18 +35,18 @@ struct OnboardingFooterView: View {
|
|||
}
|
||||
}
|
||||
.primaryButtonStyle
|
||||
.frame(height: proxy.size.height / 12)
|
||||
.padding(.horizontal, 15)
|
||||
.frame(height: 69)
|
||||
.padding(.horizontal, 28)
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
ProgressView(
|
||||
"\(viewStore.index + 1)/\(viewStore.steps.count)",
|
||||
"0\(viewStore.index + 1)",
|
||||
value: Double(viewStore.index + 1),
|
||||
total: Double(viewStore.steps.count)
|
||||
)
|
||||
.onboardingProgressStyle
|
||||
.padding(.horizontal, 30)
|
||||
.padding(.horizontal, 28)
|
||||
.padding([.vertical], 20)
|
||||
}
|
||||
}
|
||||
|
@ -64,10 +64,17 @@ struct OnboardingFooterView_Previews: PreviewProvider {
|
|||
|
||||
Group {
|
||||
OnboardingFooterView(store: store)
|
||||
.applyScreenBackground()
|
||||
.preferredColorScheme(.light)
|
||||
.previewDevice("iPhone 13 Pro Max")
|
||||
|
||||
OnboardingFooterView(store: store)
|
||||
.applyScreenBackground()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDevice("iPhone 13 Pro Max")
|
||||
|
||||
OnboardingFooterView(store: store)
|
||||
.applyScreenBackground()
|
||||
.preferredColorScheme(.dark)
|
||||
.previewDevice("iPhone 13 mini")
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import SwiftUI
|
||||
|
||||
struct NavigationButtonStyle: ButtonStyle {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.frame(
|
||||
|
@ -23,6 +25,7 @@ struct NavigationButtonStyle: ButtonStyle {
|
|||
Asset.Colors.Buttons.onboardingNavigation.color
|
||||
)
|
||||
.cornerRadius(.infinity)
|
||||
.neumorphicDesign(configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,12 +42,14 @@ struct NavigationModifier_Previews: PreviewProvider {
|
|||
Button("Back") { dump("Example button") }
|
||||
.navigationButtonStyle
|
||||
.frame(width: 80, height: 40)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
.preferredColorScheme(.dark)
|
||||
|
||||
|
||||
Button("Skip") { dump("Example button") }
|
||||
.navigationButtonStyle
|
||||
.frame(width: 80, height: 40)
|
||||
.applyScreenBackground()
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// NeumorphicDesignModifier.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 21.02.2022.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// Neumorphic design is charasterictical with two shadows (light & dark) around the button
|
||||
/// Appereance in our case is influenced by two parameters:
|
||||
/// - Parameters:
|
||||
/// - colorScheme: The light is using full neumorphic design while dark is limited to soft shadow only
|
||||
/// - isPressed: When the button is pressed, there are different behaviours for light vs. dark colorScheme
|
||||
struct NeumorphicDesign: ViewModifier {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
let isPressed: Bool
|
||||
|
||||
init(_ isPressed: Bool) {
|
||||
self.isPressed = isPressed
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.shadow(
|
||||
color: Asset.Colors.Buttons.neumorphicDarkSide.color,
|
||||
radius: 10,
|
||||
x: colorScheme == .light && !isPressed ? 10 : 0,
|
||||
y: colorScheme == .light && !isPressed ? 10 : 0
|
||||
)
|
||||
.shadow(
|
||||
color: Asset.Colors.Buttons.neumorphicLightSide.color,
|
||||
radius: 10,
|
||||
x: colorScheme == .light && !isPressed ? -5 : 0,
|
||||
y: colorScheme == .light && !isPressed ? -5 : 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func neumorphicDesign(_ isPressed: Bool = false) -> some View {
|
||||
self.modifier(NeumorphicDesign(isPressed))
|
||||
}
|
||||
}
|
|
@ -26,11 +26,13 @@ struct PrimaryButton_Previews: PreviewProvider {
|
|||
.frame(width: 250, height: 50)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
.preferredColorScheme(.light)
|
||||
.applyScreenBackground()
|
||||
|
||||
Button("Primary Button") { dump("Primary button") }
|
||||
.primaryButtonStyle
|
||||
.frame(width: 250, height: 50)
|
||||
.previewLayout(.fixed(width: 300, height: 100))
|
||||
.preferredColorScheme(.dark)
|
||||
.applyScreenBackground()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ struct StandardButtonStyle: ButtonStyle {
|
|||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.shadow(color: Asset.Colors.Buttons.buttonsTitleShadow.color, radius: 2, x: 0, y: 2)
|
||||
.frame(
|
||||
minWidth: 0,
|
||||
maxWidth: .infinity,
|
||||
|
@ -25,6 +26,7 @@ struct StandardButtonStyle: ButtonStyle {
|
|||
configuration.isPressed ? pressedBackgroundColor : background
|
||||
)
|
||||
.cornerRadius(12)
|
||||
.neumorphicDesign(configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,20 +8,27 @@
|
|||
import SwiftUI
|
||||
|
||||
struct CircularFrame: View {
|
||||
private let gradient = LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Asset.Colors.Onboarding.circularFrameGradientStart.color, Asset.Colors.Onboarding.circularFrameGradientEnd.color
|
||||
]),
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
let lineWidth = proxy.size.width * 0.05
|
||||
|
||||
Circle()
|
||||
.stroke(lineWidth: lineWidth)
|
||||
.foregroundColor(Asset.Colors.Onboarding.circularFrame.color)
|
||||
// Add two points to the frame to properly mask edges
|
||||
.stroke(gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
|
||||
// Add two points to the frame to properly mask edges
|
||||
.frame(
|
||||
width: proxy.size.width - lineWidth + 2,
|
||||
height: proxy.size.height - lineWidth + 2,
|
||||
alignment: .center
|
||||
)
|
||||
// Update the offset to account for the 2 extra points
|
||||
// Update the offset to account for the 2 extra points
|
||||
.offset(x: lineWidth / 2 - 1, y: lineWidth / 2 - 1)
|
||||
.shadow(radius: 10)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue