Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve text field input #27

Closed
wants to merge 9 commits into from

Conversation

W1W1-M
Copy link

@W1W1-M W1W1-M commented Aug 17, 2022

Hi @sindresorhus
This PR is a first draft for issue #3 Validate text fields

I first tried a border color for the hex text field but it interfered with blue border when the text field is focused. Instead I added a SF symbol image that switches depending if the field is valid.

Capture d’écran 2022-08-17 à 14 32 19

Capture d’écran 2022-08-17 à 14 36 32

The implemented colorChecker class uses ObservedObject and the onChange modifier to run on user input, it also trims unwanted characters.

The code still needs to be implemented for hsl, rgb & lch text inputs. Also needs cleaning, optimizing & testing but I wanted an initial 👍 before going ahead.

Regards

PS : I have more experience with iOS app development, this is helping me get to grips with Mac app development 😄

…ngs & checking methods. Add mark or checkmark image for feedback on valid text input. Fix SwiftLint warnings.
@W1W1-M W1W1-M mentioned this pull request Aug 17, 2022
@sindresorhus
Copy link
Owner

sindresorhus commented Aug 17, 2022

Thanks for working on this. I don't think this is the right approach though.

Here's what I'm thinking:

  1. Abstract all of the hexColorView type views into a single (general) view that can be reused for each of the color inputs.
  2. The validation logic is already implemented, if let newColor = NSColor(hexString: hexColor.trimmingCharacters(in: .whitespaces)) succeeds, it means the color is valid. If not, you can show the invalid state.

I'm also not a big fan of the symbols. We don't need a success indication, just a failure one. So I think just coloring the text red if it's invalid is enough.

@W1W1-M
Copy link
Author

W1W1-M commented Aug 17, 2022

  1. Abstract all of the hexColorView type views into a single (general) view that can be reused for each of the color inputs.

Ok, I'll work on that and update accordingly

  1. The validation logic is already implemented, if let newColor = NSColor(hexString: hexColor.trimmingCharacters(in: .whitespaces)) succeeds, it means the color is valid. If not, you can show the invalid state.

I didn't realize all the logic was already there 😅 Thanks for pointing this out !

I'm also not a big fan of the symbols. We don't need a success indication, just a failure one. So I think just coloring the text red if it's invalid is enough.

Ok, simple and straightforward is good 😄 I'll update accordingly

…r text input validation. Keep SwiftLint warning fixes.
…ndition to change text color if color code is invalid. Add condition to limit character input. Add textColor to NativeTextField for use in updateNSView method. Update other ColorViews to use textColor.
@W1W1-M W1W1-M marked this pull request as draft August 18, 2022 20:25
@W1W1-M
Copy link
Author

W1W1-M commented Aug 18, 2022

So this is still WIP, but some feedback to know if this is more inline with the proposed approach would be nice 😃

As for hex codes: they are limited to a small set of characters and also in length, I've added some code to filter and trim hex inputs (ex: #123456, FFFFFF, etc). This helps users input only valid hex codes but also makes the input rigid and removes the need to indicate (in red) an invalid input. Maybe a compromise of just trimming excess characters without filtering is a good balance ?

case rgb = "RGB"
case lch = "LCH"
}
var colorKeyboardShortcut: KeyEquivalent {
Copy link
Owner

@sindresorhus sindresorhus Aug 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be on a private extension on the ColorFormat enum, placed in this file.

Copy link
Author

@W1W1-M W1W1-M Aug 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved keyboardShortcut into an extension of ColorFormat within ColorPickerScreen.

Would it be better to centralise this extension in Constants as not to have class extensions in different files ? or is it better to keep here, in order to limit access control of extension to this file only ?

@sindresorhus
Copy link
Owner

So this is still WIP, but some feedback to know if this is more inline with the proposed approach would be nice 😃

Yes, it's the correct direction.

@sindresorhus
Copy link
Owner

This helps users input only valid hex codes but also makes the input rigid

Trimming whitespace is ok, but I don't think we should prevent some characters. Users will not understand that it's because it's not valid Hex. I think it's better to just make it red in that case.

@W1W1-M
Copy link
Author

W1W1-M commented Aug 19, 2022

On user color input, updateColorsFromPanel method needs to be called to update other color codes. This method is currently in ColorPickerScreen. In order to be able to call it from each ColorInputView I believe it needs to be moved to AppState EnvironmentObject or some other class, along with required properties (as @Published properties) ?
Or maybe there is smarter way to call it ?
Or does it need a bigger rethink as the existing comment indicates:

TODO: Find a better way to handle this.

What's your opinion on this @sindresorhus ?

…properties. Move updateColorsFromPanel method to AppState. Refactor ColorInputView & ColorPickerScreen to use @published properties from AppState. Delete unused textColor variable. Update comments.
@Published var hslColor = ""
@Published var rgbColor = ""
@Published var lchColor = ""
@Published var isPreventingUpdate = false
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these moved to AppState? AppState is for state that is required globally. This state is only needed for picker screen. I think you can just pass updateColorsFromPanel to the ColorPickerScreen view.

@@ -127,22 +127,149 @@ private struct BarView: View {
}
}

struct ColorInputView: View {
@EnvironmentObject private var appState: AppState
@State private var textColor: Color = .primary
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use an optional and default to nil.

textColor: textColor
)
.controlSize(.large)
.onChange(of: inputColorText) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the body out into a private function on the view. It makes it easier to see just the view logic.


if !appState.isPreventingUpdate {
appState.updateColorsFromPanel(excludeLCH: true, preventUpdate: true, color: colorPanel.color)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of duplicated logic in each case here. Would be good to reduce that.

For example, appState.updateColorsFromPanel could be updated to accept an enum instead of excludeLCH: true type parameters.

ColorInputView(
inputColorText: $appState.lchColor,
isTextFieldFocused: $isTextFieldFocusedLCH,
colorPanel: colorPanel,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color panel is available on the AppState, so you can access it from there instead.

isTextFieldFocused: $isTextFieldFocusedLCH,
colorPanel: colorPanel,
textFieldFontSize: textFieldFontSize,
inputColorType: .lch
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameter should be first.

.keyboardShortcut("l", modifiers: [.shift, .command])
}
}

var body: some View {
VStack {
BarView()
if shownColorFormats.contains(.hex) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just loop over the enum instead of manually defining each view. That was the point of extracting it into a reusable view.

@@ -1021,6 +1021,7 @@ struct NativeTextField: NSViewRepresentable {
var isFirstResponder = false
@Binding var isFocused: Bool // Note: This is only readable.
var isSingleLine = true
var textColor: Color
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Should be below font.

@State private var hexColor = ""
@State private var hslColor = ""
@State private var rgbColor = ""
@State private var lchColor = ""
@State private var isTextFieldFocusedHex = false
@State private var isTextFieldFocusedHSL = false
@State private var isTextFieldFocusedRGB = false
@State private var isTextFieldFocusedLCH = false
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a single state that accepts an optional ColorFormat.

@sindresorhus
Copy link
Owner

Closing for lack of activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants