iOS - User Sign In Unmask Password (#1011)
This commit is contained in:
parent
6c0ccddbd1
commit
437f7a3995
|
@ -665,6 +665,7 @@
|
||||||
E1CD13EF28EF364100CB46CA /* DetectOrientationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */; };
|
E1CD13EF28EF364100CB46CA /* DetectOrientationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */; };
|
||||||
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */; };
|
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */; };
|
||||||
E1CFE28028FA606800B7D34C /* ChapterTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CFE27F28FA606800B7D34C /* ChapterTrack.swift */; };
|
E1CFE28028FA606800B7D34C /* ChapterTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CFE27F28FA606800B7D34C /* ChapterTrack.swift */; };
|
||||||
|
E1D27EE72BBC955F00152D16 /* UnmaskSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D27EE62BBC955F00152D16 /* UnmaskSecureField.swift */; };
|
||||||
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043428D1763100587289 /* SeeAllButton.swift */; };
|
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043428D1763100587289 /* SeeAllButton.swift */; };
|
||||||
E1D3044428D1991900587289 /* LibraryViewTypeToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */; };
|
E1D3044428D1991900587289 /* LibraryViewTypeToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */; };
|
||||||
E1D37F482B9C648E00343D2B /* MaxHeightText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D37F472B9C648E00343D2B /* MaxHeightText.swift */; };
|
E1D37F482B9C648E00343D2B /* MaxHeightText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D37F472B9C648E00343D2B /* MaxHeightText.swift */; };
|
||||||
|
@ -1246,6 +1247,7 @@
|
||||||
E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectOrientationModifier.swift; sourceTree = "<group>"; };
|
E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectOrientationModifier.swift; sourceTree = "<group>"; };
|
||||||
E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
||||||
E1CFE27F28FA606800B7D34C /* ChapterTrack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterTrack.swift; sourceTree = "<group>"; };
|
E1CFE27F28FA606800B7D34C /* ChapterTrack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterTrack.swift; sourceTree = "<group>"; };
|
||||||
|
E1D27EE62BBC955F00152D16 /* UnmaskSecureField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnmaskSecureField.swift; sourceTree = "<group>"; };
|
||||||
E1D3043428D1763100587289 /* SeeAllButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeeAllButton.swift; sourceTree = "<group>"; };
|
E1D3043428D1763100587289 /* SeeAllButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeeAllButton.swift; sourceTree = "<group>"; };
|
||||||
E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewTypeToggle.swift; sourceTree = "<group>"; };
|
E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewTypeToggle.swift; sourceTree = "<group>"; };
|
||||||
E1D37F472B9C648E00343D2B /* MaxHeightText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxHeightText.swift; sourceTree = "<group>"; };
|
E1D37F472B9C648E00343D2B /* MaxHeightText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxHeightText.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1855,6 +1857,7 @@
|
||||||
E1D3043428D1763100587289 /* SeeAllButton.swift */,
|
E1D3043428D1763100587289 /* SeeAllButton.swift */,
|
||||||
E1D5C39728DF914100CDBEFB /* Slider */,
|
E1D5C39728DF914100CDBEFB /* Slider */,
|
||||||
E1581E26291EF59800D6C640 /* SplitContentView.swift */,
|
E1581E26291EF59800D6C640 /* SplitContentView.swift */,
|
||||||
|
E1D27EE62BBC955F00152D16 /* UnmaskSecureField.swift */,
|
||||||
E157562F29355B7900976E1F /* UpdateView.swift */,
|
E157562F29355B7900976E1F /* UpdateView.swift */,
|
||||||
E192607F28D28AAD002314B4 /* UserProfileButton.swift */,
|
E192607F28D28AAD002314B4 /* UserProfileButton.swift */,
|
||||||
);
|
);
|
||||||
|
@ -3873,6 +3876,7 @@
|
||||||
E1AD104D26D96CE3003E4A08 /* BaseItemDto.swift in Sources */,
|
E1AD104D26D96CE3003E4A08 /* BaseItemDto.swift in Sources */,
|
||||||
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
||||||
535870AD2669D8DD00D05A09 /* ItemFilterCollection.swift in Sources */,
|
535870AD2669D8DD00D05A09 /* ItemFilterCollection.swift in Sources */,
|
||||||
|
E1D27EE72BBC955F00152D16 /* UnmaskSecureField.swift in Sources */,
|
||||||
E1CAF65D2BA345830087D991 /* MediaType.swift in Sources */,
|
E1CAF65D2BA345830087D991 /* MediaType.swift in Sources */,
|
||||||
E1AD105F26D9ADDD003E4A08 /* NameGuidPair.swift in Sources */,
|
E1AD105F26D9ADDD003E4A08 /* NameGuidPair.swift in Sources */,
|
||||||
E18A8E7D28D606BE00333B9A /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
E18A8E7D28D606BE00333B9A /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UnmaskSecureField: UIViewRepresentable {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
private var text: String
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
|
||||||
|
init(_ title: String, text: Binding<String>) {
|
||||||
|
self.title = title
|
||||||
|
self._text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> some UIView {
|
||||||
|
|
||||||
|
let textField = UITextField()
|
||||||
|
textField.isSecureTextEntry = true
|
||||||
|
textField.keyboardType = .asciiCapable
|
||||||
|
textField.placeholder = title
|
||||||
|
textField.text = text
|
||||||
|
textField.addTarget(context.coordinator, action: #selector(Coordinator.textDidChange), for: .editingChanged)
|
||||||
|
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
button.addTarget(context.coordinator, action: #selector(Coordinator.buttonPressed), for: .touchUpInside)
|
||||||
|
button.setImage(UIImage(systemName: "eye.fill"), for: .normal)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
button.heightAnchor.constraint(equalToConstant: 50),
|
||||||
|
button.widthAnchor.constraint(equalToConstant: 50),
|
||||||
|
])
|
||||||
|
|
||||||
|
textField.rightView = button
|
||||||
|
textField.rightViewMode = .always
|
||||||
|
|
||||||
|
context.coordinator.button = button
|
||||||
|
context.coordinator.textField = textField
|
||||||
|
context.coordinator.textDidChange()
|
||||||
|
context.coordinator.textBinding = _text
|
||||||
|
|
||||||
|
return textField
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIViewType, context: Context) {}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coordinator {
|
||||||
|
|
||||||
|
weak var button: UIButton?
|
||||||
|
weak var textField: UITextField?
|
||||||
|
var textBinding: Binding<String> = .constant("")
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func buttonPressed() {
|
||||||
|
guard let textField else { return }
|
||||||
|
textField.toggleSecureEntry()
|
||||||
|
|
||||||
|
let eye = textField.isSecureTextEntry ? "eye.fill" : "eye.slash"
|
||||||
|
button?.setImage(UIImage(systemName: eye), for: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func textDidChange() {
|
||||||
|
guard let textField, let text = textField.text else { return }
|
||||||
|
button?.isEnabled = !text.isEmpty
|
||||||
|
textBinding.wrappedValue = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UITextField {
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/48115361
|
||||||
|
func toggleSecureEntry() {
|
||||||
|
|
||||||
|
isSecureTextEntry.toggle()
|
||||||
|
|
||||||
|
if let existingText = text, isSecureTextEntry {
|
||||||
|
deleteBackward()
|
||||||
|
|
||||||
|
if let textRange = textRange(from: beginningOfDocument, to: endOfDocument) {
|
||||||
|
replace(textRange, withText: existingText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let existingSelectedTextRange = selectedTextRange {
|
||||||
|
selectedTextRange = nil
|
||||||
|
selectedTextRange = existingSelectedTextRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,12 +32,12 @@ struct UserSignInView: View {
|
||||||
private var signInSection: some View {
|
private var signInSection: some View {
|
||||||
Section {
|
Section {
|
||||||
TextField(L10n.username, text: $username)
|
TextField(L10n.username, text: $username)
|
||||||
.disableAutocorrection(true)
|
.autocorrectionDisabled()
|
||||||
.autocapitalization(.none)
|
.textInputAutocapitalization(.none)
|
||||||
|
|
||||||
SecureField(L10n.password, text: $password)
|
UnmaskSecureField(L10n.password, text: $password)
|
||||||
.disableAutocorrection(true)
|
.autocorrectionDisabled()
|
||||||
.autocapitalization(.none)
|
.textInputAutocapitalization(.none)
|
||||||
|
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
|
|
Loading…
Reference in New Issue