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

Validate empty inputs and allow flag and dial code to be shown when field is empty #27

Closed
davidjaldred opened this issue Sep 14, 2021 · 19 comments

Comments

@davidjaldred
Copy link

davidjaldred commented Sep 14, 2021

Hey, love the package, best I've found for a phone input in Flutter.

2 things:

  1. What's the recommended way to require the field? I'd like to validate empty inputs as errors (I can see there is some discussion of this in feat(validator): allow to use custom validator #16).
  2. Is it possible to always show the button with the flag and dial code in the input field, rather than only when it has focus or input?

Thanks.

@cedvdb
Copy link
Owner

cedvdb commented Sep 14, 2021

  1. That pull request is soon passing, we just got to figure out something first, so I guess the best is to wait a day or two.
  2. It's not currently possible no. If I recall correctly doing so was messing with the alignments. Something can be worked out though, the easy way to implement it was to do it this way at first.

I'm glad you like the package, stay tuned as @emri99 made some nice contributions which I hope to release this week

@davidjaldred
Copy link
Author

  1. Brilliant, thanks!
  2. No worries, may have a look myself. Just realised my issue was a bit confusing actually... I'm not concerned about showing the button when the input isn't focused. Instead when the input is empty, I'd like to present it in the same way with the flag and the dial code as it's shown when numbers have actually been entered into the field, like this:
    image
    So the flag and dial code would also be shown instead of this gap when the field is empty and just showing my hint text:
    image

That's awesome to hear! This package definitely deserves more likes. I can see it's fairly new. Hope it starts to appear higher in the search results on pub.dev soon!

@davidjaldred davidjaldred changed the title Validate empty inputs and allow input button to always be shown Validate empty inputs and allow flag and dial code to be shown when field is empty Sep 15, 2021
@cedvdb
Copy link
Owner

cedvdb commented Sep 15, 2021

So this should be added for when the floatingLabelBehavior is set to floatingLabelBehavior: FloatingLabelBehavior.always,, is that right ?

@cedvdb cedvdb mentioned this issue Sep 15, 2021
@cedvdb
Copy link
Owner

cedvdb commented Sep 15, 2021

WIP PR for

#28

But the grayed out flag won't look good on background like yours, so I'm trying to figure out an alternative solution

@davidjaldred
Copy link
Author

So this should be added for when the floatingLabelBehavior is set to floatingLabelBehavior: FloatingLabelBehavior.always,, is that right ?

Oh no, that isn't a real label, just a text widget I'm using as a custom label. It can be ignored 😊 My real concern is just that I want to always show the dial code and flag, even if the input is empty. When I enter anything into the field, the dial code and flag are always shown, and it would be perfect if it was always like that, even when empty.

Here's a mockup of how I'd like to be able to get it to look when empty (the 'Enter your phone number' is hint text, and the field is not focused):
image

@cedvdb
Copy link
Owner

cedvdb commented Sep 16, 2021

Can you try out the branch fixed_dialcode by editing your pubspec to use it ?

@davidjaldred
Copy link
Author

Can you try out the branch fixed_dialcode by editing your pubspec to use it ?

Just tested and this has exactly the behaviour I was looking for 😁

Only problem now is that the alignment is off (which you initially mentioned was a problem). The alignment is also off when the field isn't empty too (which was okay before).

image
image

Widget inspector in devtools shows this:
image

Thanks for all your help with this issue so far!

@cedvdb
Copy link
Owner

cedvdb commented Sep 16, 2021

hmm weird, that's not the behavior I'm having. Could you share the code for the phone input so I can try to reproduce ? There might be a padding around it or something affecting the end result ?

@davidjaldred
Copy link
Author

Sure. Maybe I'm not grabbing the package from github properly. Here's a simplified version in a fresh Flutter project, which is still showing the issue (even removing the theme and surrounding widgets doesn't seem to affect the alignment unfortunately):

pubspec.yaml dependency

  phone_form_field:
    git:
      url: https://github.com/cedvdb/phone_form_field.git
      ref: fixed_dialcode

phone_form_field_test.dart

import 'package:flutter/material.dart';
import 'package:phone_form_field/phone_form_field.dart';

class PhoneFormFieldTest extends StatelessWidget {
  const PhoneFormFieldTest({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(bottom: 24),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            margin: const EdgeInsets.only(bottom: 4),
            child: Text(
              'Phone Number'.toUpperCase(),
              style: Theme.of(context).textTheme.overline!.apply(
                    color: Colors.white.withAlpha(153),
                  ),
            ),
          ),
          PhoneFormField(
            defaultCountry: 'GB',
            errorText: 'Enter a valid phone number',
            selectorNavigator: const ModalBottomSheetNavigator(),
            decoration: const InputDecoration(
              hintText: 'Enter your phone number',
            ),
          ),
        ],
      ),
    );
  }
}

main.dart

import 'package:flutter/material.dart';
import 'phone_form_field_test.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        brightness: Brightness.dark,
        visualDensity: VisualDensity.adaptivePlatformDensity,
        primarySwatch: const MaterialColor(
          0xff3305ff,
          {
            50: Color(0xffebe5ff),
            100: Color(0xffdad1ff),
            200: Color(0xffb09eff),
            300: Color(0xff866bff),
            400: Color(0xff5d38ff),
            500: Color(0xff3305ff),
            600: Color(0xff2600d1),
            700: Color(0xff1d009e),
            800: Color(0xff14006b),
            900: Color(0xff0a0038),
          },
        ),
        accentColor: const Color(0xffffb53d),
        errorColor: const Color(0xffff3d6e),
        toggleableActiveColor: const Color(0xffffb53d),
        canvasColor: Color.alphaBlend(
          const Color(0xffb09eff).withAlpha(26),
          Colors.black,
        ),
        scaffoldBackgroundColor: Colors.black,
        textSelectionTheme: TextSelectionThemeData(
          cursorColor: const Color(0xffffb53d),
          selectionColor: const Color(0xffffb53d).withAlpha(102),
          selectionHandleColor: const Color(0xffffb53d),
        ),
        fontFamily: 'Inter',
        textTheme: Theme.of(context)
            .textTheme
            .copyWith(
              headline1: const TextStyle(
                fontSize: 4.209 * 16,
                fontWeight: FontWeight.w700,
                letterSpacing: -0.03 * 16,
                height: 1.25,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              headline2: const TextStyle(
                fontSize: 3.157 * 16,
                fontWeight: FontWeight.w700,
                letterSpacing: -0.03 * 16,
                height: 1.25,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              headline3: const TextStyle(
                fontSize: 2.369 * 16,
                fontWeight: FontWeight.w700,
                letterSpacing: -0.02 * 16,
                height: 1.375,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              headline4: const TextStyle(
                fontSize: 1.777 * 16,
                fontWeight: FontWeight.w700,
                letterSpacing: -0.02 * 16,
                height: 1.375,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              headline5: const TextStyle(
                fontSize: 1.333 * 16,
                fontWeight: FontWeight.w700,
                letterSpacing: -0.01 * 16,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              headline6: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w700,
                letterSpacing: -0.01 * 16,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              subtitle1: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                letterSpacing: -0.01 * 16,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              subtitle2: const TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.w700,
                letterSpacing: 0,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              bodyText1: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w400,
                letterSpacing: -0.01 * 16,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              bodyText2: const TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.w400,
                letterSpacing: 0,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              button: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w700,
                letterSpacing: 0,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              caption: const TextStyle(
                fontSize: 14,
                fontWeight: FontWeight.w400,
                letterSpacing: 0,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
              overline: const TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.w700,
                letterSpacing: 0.03 * 16,
                height: 1.5,
                leadingDistribution: TextLeadingDistribution.even,
              ),
            )
            .apply(
              displayColor: Colors.white,
              bodyColor: Colors.white,
            ),
        inputDecorationTheme: InputDecorationTheme(
          labelStyle: TextStyle(
            color: Colors.white.withAlpha(153),
          ),
          helperStyle: TextStyle(
            color: Colors.white.withAlpha(153),
          ),
          hintStyle: TextStyle(
            color: Colors.white.withAlpha(153),
          ),
          errorMaxLines: 9,
          filled: true,
          fillColor: const Color(0xffb09eff).withAlpha(26),
          hoverColor: const Color(0xffb09eff).withAlpha(13),
          errorBorder: UnderlineInputBorder(
            borderSide: BorderSide(
              color: const Color(0xffff3d6e).withAlpha(128),
              width: 3,
            ),
            borderRadius: BorderRadius.zero,
          ),
          focusedBorder: const UnderlineInputBorder(
            borderSide: BorderSide(
              color: Color(0xffffb53d),
              width: 3,
            ),
            borderRadius: BorderRadius.zero,
          ),
          focusedErrorBorder: const UnderlineInputBorder(
            borderSide: BorderSide(
              color: Color(0xffff3d6e),
              width: 3,
            ),
            borderRadius: BorderRadius.zero,
          ),
          disabledBorder: const UnderlineInputBorder(
            borderRadius: BorderRadius.zero,
          ),
          enabledBorder: UnderlineInputBorder(
            borderSide: BorderSide(
              color: const Color(0xffb09eff).withAlpha(77),
              width: 3,
            ),
            borderRadius: BorderRadius.zero,
          ),
          border: UnderlineInputBorder(
            borderSide: BorderSide(
              color: const Color(0xffb09eff).withAlpha(77),
              width: 3,
            ),
            borderRadius: BorderRadius.zero,
          ),
          alignLabelWithHint: true,
        ),
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    Key? key,
  }) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: PhoneFormFieldTest(),
    );
  }
}

And here's the result:

image

@cedvdb
Copy link
Owner

cedvdb commented Sep 19, 2021

Ho, the alignment needs to be different when there is no label.

It should be fixed, but your image seems different from what I have, so could you try again ?

@davidjaldred
Copy link
Author

I think I understand what you're saying. That alignment is correct if the input has a label, here's what I get:

image

But I don't want to use a label, and I get the misalignment shown before without one. The alignment was perfect initially, I just wanted the flag and dial code to be shown when the input was empty. Is there a way to change the alignment back to what it was before?

@cedvdb
Copy link
Owner

cedvdb commented Sep 20, 2021

This should now be fixed for next release (with and without label)

@cedvdb cedvdb closed this as completed Sep 20, 2021
@davidjaldred
Copy link
Author

Awesome, thanks for the help! 😁

@cedvdb cedvdb reopened this Sep 23, 2021
@cedvdb
Copy link
Owner

cedvdb commented Sep 23, 2021

Hello, I'm reopening the issue because apparently it was still misaligned on the latest commits of dev on some devices. Could you try latest dev branch to see if that fixes to alignment issue ? Warning the API of v4 has breaking changes compared to v3. Also to have a fixed country code you can use countryCodeVisibility param but it the default (auto) should be fixed if you have no label, which you don't.

If that does not work I will give up as flutter does not furnish a nice way to align things in the textfield. It is a nightmare and you won't believe the time I spent on that ( I can barely believe it myself).

It seems the alignment changes from platform to platform, if there is a label or not, if the border is outline or not, if a prefix is used or a prefixIcon this is too much cases to cover faithfully imo but maybe it will work.

@davidjaldred
Copy link
Author

Sure. First things first. Turns out the Flutter channel I was on had completely messed up and was using one of the Flutter developer's forks - I have no idea how this could have happened as I'm pretty meticulous about this stuff and never switched it from the stable channel. Switching back to stable, the alignment on that fixed_dialcode branch was not as bad as my previous image showed... although it was still slightly misaligned:

fixed_dialcode

image

So I tried the dev branch. Had to add intl as a dependency (assuming this is because i'm grabbing your package from github).
And the alignment is looking a bit better:

dev

image
image

It is a nightmare and you won't believe the time I spent on that ( I can barely believe it myself).

I can believe it and I feel your pain! Honestly, thanks for all the work you've put towards this issue.

Out of interest, how is the alignment so good in the current version 3.0.1? Sorry I haven't had the time to look at the code yet. I just find it interesting that I can insert a character into the field and it gives the desired functionality (I started to think of a hacky solution where I insert a character if the field is empty and then hide it somehow and show the hint text instead haha). The horizontal alignment and text color for the dial code looks nice in 3.0.1 too.

3.0.1

image
image

@cedvdb
Copy link
Owner

cedvdb commented Sep 27, 2021

So to answer your question of why it is so hard and why it was looking good before is:

  • previously the parameter prefix of input decoration was used. While this aligns perfectly it's not possible to always show the prefix. Flutter only allows for the prefix to be shown when on focus.
  • They added a prefixIcon parameter which is always shown. However the alignement is not aligned to the text. While this seems like a minor issue that can be fixed with a bit of tweaking it actually is not. That is because the text alignment changes a lot depending on your configuration.

A better fix that I could introduce is to allow the user to align the dial code himself but I'd rather the widget to be a set and done type than needing configuration.

Maybe setting an empty label: Text('') can fixe the alignment issue. That might also change the padding under the text too as a side effect (maybe not).

@davidjaldred
Copy link
Author

Ahh okay, so the underlying issue is with Flutter. I remember reading about an issue with the alignment of prefixIcon before, think it was: flutter/flutter#42651

Thanks for your explanation, suggestions, and work on the issue.

@cedvdb
Copy link
Owner

cedvdb commented Oct 3, 2021

I reverted the changes I made for this to release v4.0.0 but posted an issue on flutter. Hopefully feedback from their side or even a fix will lead to something here. In the meantime there is nothing I can do

@cedvdb
Copy link
Owner

cedvdb commented Oct 15, 2021

I'm closing this one since it is partially resolved and opening a new one which is about the unresolved part

@cedvdb cedvdb closed this as completed Oct 15, 2021
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 a pull request may close this issue.

2 participants