commit
b790c03adf
@ -14,6 +14,8 @@
|
|||||||
02AE650029363DC1005A4AF3 /* BeRealTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE64FF29363DC1005A4AF3 /* BeRealTests.swift */; };
|
02AE650029363DC1005A4AF3 /* BeRealTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE64FF29363DC1005A4AF3 /* BeRealTests.swift */; };
|
||||||
02AE650A29363DC1005A4AF3 /* BeRealUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE650929363DC1005A4AF3 /* BeRealUITests.swift */; };
|
02AE650A29363DC1005A4AF3 /* BeRealUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE650929363DC1005A4AF3 /* BeRealUITests.swift */; };
|
||||||
02AE650C29363DC1005A4AF3 /* BeRealUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE650B29363DC1005A4AF3 /* BeRealUITestsLaunchTests.swift */; };
|
02AE650C29363DC1005A4AF3 /* BeRealUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02AE650B29363DC1005A4AF3 /* BeRealUITestsLaunchTests.swift */; };
|
||||||
|
02FFFD7B29395DD200306533 /* String+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FFFD7A29395DD200306533 /* String+Constants.swift */; };
|
||||||
|
4694AAA0293A7C8800D54903 /* Modules in Frameworks */ = {isa = PBXBuildFile; productRef = 4694AA9F293A7C8800D54903 /* Modules */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -34,6 +36,7 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
02784F03293A8331005F839D /* Modules */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Modules; sourceTree = "<group>"; };
|
||||||
02AE64EB29363DBF005A4AF3 /* BeReal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeReal.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
02AE64EB29363DBF005A4AF3 /* BeReal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeReal.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeRealApp.swift; sourceTree = "<group>"; };
|
02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeRealApp.swift; sourceTree = "<group>"; };
|
||||||
02AE64F029363DBF005A4AF3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
02AE64F029363DBF005A4AF3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
@ -44,6 +47,7 @@
|
|||||||
02AE650529363DC1005A4AF3 /* BeRealUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BeRealUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
02AE650529363DC1005A4AF3 /* BeRealUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BeRealUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
02AE650929363DC1005A4AF3 /* BeRealUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeRealUITests.swift; sourceTree = "<group>"; };
|
02AE650929363DC1005A4AF3 /* BeRealUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeRealUITests.swift; sourceTree = "<group>"; };
|
||||||
02AE650B29363DC1005A4AF3 /* BeRealUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeRealUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
02AE650B29363DC1005A4AF3 /* BeRealUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeRealUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||||
|
02FFFD7A29395DD200306533 /* String+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Constants.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -51,6 +55,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4694AAA0293A7C8800D54903 /* Modules in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -71,13 +76,22 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
027F60592937662300467238 /* Login */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
path = Login;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
02AE64E229363DBF005A4AF3 = {
|
02AE64E229363DBF005A4AF3 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
02784F03293A8331005F839D /* Modules */,
|
||||||
02AE64ED29363DBF005A4AF3 /* BeReal */,
|
02AE64ED29363DBF005A4AF3 /* BeReal */,
|
||||||
02AE64FE29363DC1005A4AF3 /* BeRealTests */,
|
02AE64FE29363DC1005A4AF3 /* BeRealTests */,
|
||||||
02AE650829363DC1005A4AF3 /* BeRealUITests */,
|
02AE650829363DC1005A4AF3 /* BeRealUITests */,
|
||||||
02AE64EC29363DBF005A4AF3 /* Products */,
|
02AE64EC29363DBF005A4AF3 /* Products */,
|
||||||
|
4694AA9E293A7C8800D54903 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@ -94,6 +108,8 @@
|
|||||||
02AE64ED29363DBF005A4AF3 /* BeReal */ = {
|
02AE64ED29363DBF005A4AF3 /* BeReal */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
02FFFD7929395DBF00306533 /* Extensions */,
|
||||||
|
027F60592937662300467238 /* Login */,
|
||||||
02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */,
|
02AE64EE29363DBF005A4AF3 /* BeRealApp.swift */,
|
||||||
02AE64F029363DBF005A4AF3 /* ContentView.swift */,
|
02AE64F029363DBF005A4AF3 /* ContentView.swift */,
|
||||||
02AE64F229363DC1005A4AF3 /* Assets.xcassets */,
|
02AE64F229363DC1005A4AF3 /* Assets.xcassets */,
|
||||||
@ -127,6 +143,21 @@
|
|||||||
path = BeRealUITests;
|
path = BeRealUITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
02FFFD7929395DBF00306533 /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
02FFFD7A29395DD200306533 /* String+Constants.swift */,
|
||||||
|
);
|
||||||
|
path = Extensions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
4694AA9E293A7C8800D54903 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -143,6 +174,9 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = BeReal;
|
name = BeReal;
|
||||||
|
packageProductDependencies = (
|
||||||
|
4694AA9F293A7C8800D54903 /* Modules */,
|
||||||
|
);
|
||||||
productName = BeReal;
|
productName = BeReal;
|
||||||
productReference = 02AE64EB29363DBF005A4AF3 /* BeReal.app */;
|
productReference = 02AE64EB29363DBF005A4AF3 /* BeReal.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
@ -260,6 +294,7 @@
|
|||||||
files = (
|
files = (
|
||||||
02AE64F129363DBF005A4AF3 /* ContentView.swift in Sources */,
|
02AE64F129363DBF005A4AF3 /* ContentView.swift in Sources */,
|
||||||
02AE64EF29363DBF005A4AF3 /* BeRealApp.swift in Sources */,
|
02AE64EF29363DBF005A4AF3 /* BeRealApp.swift in Sources */,
|
||||||
|
02FFFD7B29395DD200306533 /* String+Constants.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -608,6 +643,13 @@
|
|||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
4694AA9F293A7C8800D54903 /* Modules */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Modules;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 02AE64E329363DBF005A4AF3 /* Project object */;
|
rootObject = 02AE64E329363DBF005A4AF3 /* Project object */;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Login
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -17,6 +18,9 @@ struct ContentView: View {
|
|||||||
Text("Hello, world!")
|
Text("Hello, world!")
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
.sheet(isPresented: .constant(true)) {
|
||||||
|
LoginView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
BeReal/Extensions/String+Constants.swift
Normal file
11
BeReal/Extensions/String+Constants.swift
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// String+Constants.swift
|
||||||
|
// BeReal
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 01/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
static let empty = ""
|
||||||
|
}
|
25
Modules/Package.swift
Normal file
25
Modules/Package.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// swift-tools-version: 5.7
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Modules",
|
||||||
|
defaultLocalization: "en",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v15)
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.library(
|
||||||
|
name: "Modules",
|
||||||
|
targets: [
|
||||||
|
"Login"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "Login",
|
||||||
|
resources: [.process("Resources")]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
12
Modules/Sources/Login/Resources/en.lproj/Localizable.strings
Normal file
12
Modules/Sources/Login/Resources/en.lproj/Localizable.strings
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
Localizable.strings
|
||||||
|
Login
|
||||||
|
|
||||||
|
Created by Javier Cicchelli on 02/12/2022.
|
||||||
|
Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"login.title.text" = "My files";
|
||||||
|
"login.text_field.username.placeholder" = "Username";
|
||||||
|
"login.text_field.password.placeholder" = "Password";
|
||||||
|
"login.button.log_in.text" = "Log in";
|
154
Modules/Sources/Login/UI/Components/LoginForm.swift
Normal file
154
Modules/Sources/Login/UI/Components/LoginForm.swift
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
//
|
||||||
|
// LoginForm.swift
|
||||||
|
// Login
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 01/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoginForm: View {
|
||||||
|
|
||||||
|
// MARK: States
|
||||||
|
|
||||||
|
@FocusState private var focusedField: Field?
|
||||||
|
|
||||||
|
// MARK: Bindings
|
||||||
|
|
||||||
|
@Binding var username: String
|
||||||
|
@Binding var password: String
|
||||||
|
@Binding var errorMessage: String?
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
|
let onReturn: () -> Void
|
||||||
|
|
||||||
|
// MARK: Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(
|
||||||
|
alignment: .leading,
|
||||||
|
spacing: 16
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
NSLocalizedString(
|
||||||
|
"login.text_field.username.placeholder",
|
||||||
|
bundle: .module,
|
||||||
|
comment: "The placeholder for the username text field."
|
||||||
|
),
|
||||||
|
text: $username
|
||||||
|
) { isBeginEditing in
|
||||||
|
guard isBeginEditing, errorMessage != nil else { return }
|
||||||
|
|
||||||
|
errorMessage = nil
|
||||||
|
}
|
||||||
|
.textContentType(.username)
|
||||||
|
.lineLimit(1)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.keyboardType(.default)
|
||||||
|
.focused($focusedField, equals: .username)
|
||||||
|
.onSubmit {
|
||||||
|
onUsernameReturnPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
SecureField(
|
||||||
|
NSLocalizedString(
|
||||||
|
"login.text_field.password.placeholder",
|
||||||
|
bundle: .module,
|
||||||
|
comment: "The placeholder for the password text field."
|
||||||
|
),
|
||||||
|
text: $password
|
||||||
|
)
|
||||||
|
.textContentType(.password)
|
||||||
|
.lineLimit(1)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.keyboardType(.default)
|
||||||
|
.focused($focusedField, equals: .password)
|
||||||
|
.onSubmit {
|
||||||
|
onPasswordReturnPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let errorMessage {
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
Text(errorMessage)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(16)
|
||||||
|
.background(Color.primary.colorInvert())
|
||||||
|
.cornerRadius(8)
|
||||||
|
.onAppear {
|
||||||
|
setClearButtonIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension LoginForm {
|
||||||
|
func setClearButtonIfNeeded() {
|
||||||
|
let textFieldAppearance = UITextField.appearance()
|
||||||
|
guard textFieldAppearance.clearButtonMode != .whileEditing else { return }
|
||||||
|
|
||||||
|
textFieldAppearance.clearButtonMode = .whileEditing
|
||||||
|
}
|
||||||
|
|
||||||
|
func onUsernameReturnPressed() {
|
||||||
|
guard !username.isEmpty else { return }
|
||||||
|
|
||||||
|
if password.isEmpty {
|
||||||
|
focusedField = .password
|
||||||
|
} else {
|
||||||
|
onReturn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onPasswordReturnPressed() {
|
||||||
|
guard !password.isEmpty else { return }
|
||||||
|
|
||||||
|
if username.isEmpty {
|
||||||
|
focusedField = .username
|
||||||
|
} else {
|
||||||
|
onReturn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Enumerations
|
||||||
|
|
||||||
|
private extension LoginForm {
|
||||||
|
enum Field: Hashable {
|
||||||
|
case username
|
||||||
|
case password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct LoginForm_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LoginForm(
|
||||||
|
username: .constant("Some username"),
|
||||||
|
password: .constant("Some Password"),
|
||||||
|
errorMessage: .constant(nil),
|
||||||
|
onReturn: {}
|
||||||
|
)
|
||||||
|
.previewDisplayName("Login form with no error message")
|
||||||
|
|
||||||
|
LoginForm(
|
||||||
|
username: .constant("Some username"),
|
||||||
|
password: .constant("Some Password"),
|
||||||
|
errorMessage: .constant("Some error goes in here..."),
|
||||||
|
onReturn: {}
|
||||||
|
)
|
||||||
|
.previewDisplayName("Login form with some error message")
|
||||||
|
}
|
||||||
|
}
|
22
Modules/Sources/Login/UI/Components/ViewHeightGeometry.swift
Normal file
22
Modules/Sources/Login/UI/Components/ViewHeightGeometry.swift
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// ViewHeightGeometry.swift
|
||||||
|
// Login
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 02/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ViewHeightGeometry: View {
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
Color.clear.preference(
|
||||||
|
key: ViewHeightPreferenceKey.self,
|
||||||
|
value: proxy.size.height
|
||||||
|
+ proxy.safeAreaInsets.top
|
||||||
|
+ proxy.safeAreaInsets.bottom
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// ViewHeightPreferenceKey.swift
|
||||||
|
// Login
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 02/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ViewHeightPreferenceKey: PreferenceKey {
|
||||||
|
static var defaultValue: CGFloat = 0
|
||||||
|
|
||||||
|
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||||
|
value = nextValue()
|
||||||
|
}
|
||||||
|
}
|
34
Modules/Sources/Login/UI/Styles/LogInLabelStyle.swift
Normal file
34
Modules/Sources/Login/UI/Styles/LogInLabelStyle.swift
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// LogInLabelStyle.swift
|
||||||
|
// Login
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 02/12/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LogInLabelStyle: LabelStyle {
|
||||||
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
configuration.title
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
configuration.icon
|
||||||
|
.tint(.primary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - LabelStyle
|
||||||
|
|
||||||
|
extension LabelStyle where Self == LogInLabelStyle {
|
||||||
|
static var logIn: Self {
|
||||||
|
LogInLabelStyle()
|
||||||
|
}
|
||||||
|
}
|
117
Modules/Sources/Login/UI/Views/LoginView.swift
Normal file
117
Modules/Sources/Login/UI/Views/LoginView.swift
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
//
|
||||||
|
// LoginView.swift
|
||||||
|
// Login
|
||||||
|
//
|
||||||
|
// Created by Javier Cicchelli on 30/11/2022.
|
||||||
|
// Copyright © 2022 Röck+Cöde. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct LoginView: View {
|
||||||
|
|
||||||
|
// MARK: States
|
||||||
|
|
||||||
|
@State private var containerTopPadding: CGFloat = 0
|
||||||
|
|
||||||
|
// MARK: Initialisers
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
// MARK: Body
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
ScrollView(
|
||||||
|
.vertical,
|
||||||
|
showsIndicators: false
|
||||||
|
) {
|
||||||
|
LoginContainer()
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
.padding(.top, containerTopPadding)
|
||||||
|
}
|
||||||
|
.background(Color.red)
|
||||||
|
.overlay(ViewHeightGeometry())
|
||||||
|
.onPreferenceChange(ViewHeightPreferenceKey.self) { height in
|
||||||
|
containerTopPadding = height * 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Views
|
||||||
|
|
||||||
|
fileprivate extension LoginView {
|
||||||
|
struct LoginContainer: View {
|
||||||
|
|
||||||
|
// MARK: States
|
||||||
|
|
||||||
|
@State private var isAuthenticating: Bool = false
|
||||||
|
@State private var username: String = ""
|
||||||
|
@State private var password: String = ""
|
||||||
|
@State private var errorMessage: String?
|
||||||
|
|
||||||
|
// MARK: Body
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 32) {
|
||||||
|
Text(
|
||||||
|
"login.title.text",
|
||||||
|
bundle: .module,
|
||||||
|
comment: "Login view title text."
|
||||||
|
)
|
||||||
|
.font(.largeTitle)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
LoginForm(
|
||||||
|
username: $username,
|
||||||
|
password: $password,
|
||||||
|
errorMessage: $errorMessage
|
||||||
|
) {
|
||||||
|
// TODO: login with the username and password.
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
// TODO: login with the username and password.
|
||||||
|
} label: {
|
||||||
|
Label {
|
||||||
|
Text(
|
||||||
|
"login.button.log_in.text",
|
||||||
|
bundle: .module,
|
||||||
|
comment: "Log in button text."
|
||||||
|
)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
} icon: {
|
||||||
|
if isAuthenticating {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.labelStyle(.logIn)
|
||||||
|
}
|
||||||
|
.tint(.orange)
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.buttonBorderShape(.roundedRectangle(radius: 8))
|
||||||
|
.controlSize(.large)
|
||||||
|
.disabled(isLoginDisabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private extension LoginView.LoginContainer {
|
||||||
|
var isLoginDisabled: Bool {
|
||||||
|
username.isEmpty || password.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Previews
|
||||||
|
|
||||||
|
struct LoginView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LoginView()
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user