CommunityToolkit / Maui

The .NET MAUI Community Toolkit is a community-created library that contains .NET MAUI Extensions, Advanced UI/UX Controls, and Behaviors to help make your life as a .NET MAUI developer easier
https://learn.microsoft.com/dotnet/communitytoolkit/maui
MIT License
2.14k stars 350 forks source link

[Proposal] Autofill Hints #373

Open coen22 opened 2 years ago

coen22 commented 2 years ago

Autofill Hints

Summary

I would like to request to add autofill to MCT. Autofill is common in HTML, where the browsers remembers a user's details and fills them in. This is also possible on the OS level in Android, iOS and (I think also) macOS.

Motivation

It's nice for users to log in with a tap on their screen and it makes it easier to sign up for within an app.

Detailed Design

I've included the implementation that I have in Xamarin Forms. The namespaces have already been renamed.

This is the API class.

using System;
using Xamarin.Forms; // should become Miscrosoft.Maui

namespace CommunityToolkit.Maui.Effects
{

    public class AutofillEffect : RoutingEffect {

        public AutofillContentType Type { get; set; }

        public AutofillEffect() : base(nameof(AutofillEffect)) {}

    }

    // TODO extend with all possibilities
    public enum AutofillContentType {
        None,
        Username,
        Password,
        NewPassword,
        Email,
        FirstName,
        Phone
    }
}

The Android class:

using System;
using System.Linq;
using Android.OS;
using Android.Views;
using Android.Widget;
using CommunityToolkit.Maui.Effects;
using CommunityToolkit.Maui.Android.Effects;
using Xamarin.Forms.Platform.Android;

[assembly: Xamarin.Forms.ResolutionGroupName("CommunityToolkit")]
[assembly: Xamarin.Forms.ExportEffect(typeof(AndroidAutofillEffect), "AutofillEffect")]

namespace CommunityToolkit.Maui.Android.Effects {

    public class AndroidAutofillEffect : PlatformEffect {

        protected override void OnAttached() {
            var effect = (AutofillEffect) Element.Effects.FirstOrDefault(e => e is AutofillEffect);
            if (effect != null
                && Build.VERSION.SdkInt >= BuildVersionCodes.O
                && Control is EditText editText) {
                switch (effect.Type) {
                    case AutofillContentType.Username:
                        editText.ImportantForAutofill = ImportantForAutofill.Yes;
                        editText.SetAutofillHints(View.AutofillHintUsername);
                        break;
                    case AutofillContentType.Password:
                        editText.ImportantForAutofill = ImportantForAutofill.Yes;
                        editText.SetAutofillHints(View.AutofillHintPassword);
                        break;
                    case AutofillContentType.Email:
                        editText.ImportantForAutofill = ImportantForAutofill.Yes;
                        editText.SetAutofillHints(View.AutofillHintEmailAddress);
                        break;
                    case AutofillContentType.FirstName:
                        editText.ImportantForAutofill = ImportantForAutofill.Yes;
                        editText.SetAutofillHints(View.AutofillHintName); // Not implemented
                        break;
                    case AutofillContentType.Phone:
                        editText.ImportantForAutofill = ImportantForAutofill.Yes;
                        editText.SetAutofillHints(View.AutofillHintPhone);
                        break;
                    default:
                        editText.ImportantForAutofill = ImportantForAutofill.Auto;
                        editText.SetAutofillHints(autofillHints: null);
                        break;
                }
            }
        }

        protected override void OnDetached() {
            if (Build.VERSION.SdkInt >= BuildVersionCodes.O
                && Control is EditText editText) {
                editText.ImportantForAutofill = ImportantForAutofill.Auto;
                editText.SetAutofillHints(autofillHints: null);
            }
        }
    }
}

The iOS (and similarly macOS) class:

using System.Linq;
using Foundation;
using UIKit;
using CommunityToolkit.Maui.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS; 

[assembly: ExportEffect(typeof(AppleAutofillEffect), "AutofillEffect")]

public class AppleAutofillEffect : PlatformEffect {

    protected override void OnAttached() {
        var effect = (AutofillEffect)Element.Effects
            .FirstOrDefault(e => e is AutofillEffect);
        if (effect != null
            && UIDevice.CurrentDevice.CheckSystemVersion(11, 0)
            && Control is UITextField textField) {
            switch (effect.Type) {
                case AutofillContentType.Username:
                    textField.TextContentType = UITextContentType.Username;
                    break;
                case AutofillContentType.Password:
                    textField.TextContentType = UITextContentType.Password;
                    break;
                case AutofillContentType.NewPassword:
                    textField.TextContentType = UITextContentType.NewPassword;
                    break;
                case AutofillContentType.Email:
                    textField.TextContentType = UITextContentType.EmailAddress;
                    break;
                case AutofillContentType.FirstName:
                    textField.TextContentType = UITextContentType.GivenName;
                    break;
                case AutofillContentType.Phone:
                    textField.TextContentType = UITextContentType.TelephoneNumber;
                    break;
                default:
                    textField.TextContentType = NSString.Empty;
                    break;
            }
        }
    }

    protected override void OnDetached() {
        if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0)
            && Control is UITextField textField) {
            textField.TextContentType = NSString.Empty;
        }
    }
}

Usage Syntax

XAML Usage

< Entry.Effects Margin="15,5" x:Name="EmailEntry"
                          Keyboard="Email" Text="{Binding Email}"
                          ReturnType="Next"
                          Placeholder="{x:Static lang:Lang.Email}">
    <Entry.Effects>
        <controls:AutofillEffect Type="Username" />
    </Entry.Effects>
</Entry.Effects >

Drawbacks

Currently the Android C# wrapper has limited support. The View class doesn't have all strings (e.g. AUTOFILL_HINT_PERSON_NAME_GIVEN), but they can be added manually. https://developer.android.com/guide/topics/text/autofill-optimize

Alternatives

N/A

Unresolved Questions

What is the 'correct' way of implementing this in MAUI? (now it's XF Compat)

Does Windows have something similar to Autofill?

brminnick commented 2 years ago

Thanks @coen22! This sounds like a cool new feature that would be useful for .NET MAUI devs!

There are two changes I'd like to make to this Proposal:

Use Behavior instead of Effect

Effects aren't recommended for .NET MAUI.

Long story short, they were a lightweight way to add functionality to Xamarin.Forms controls to avoid the overhead of creating/writing/leveraging Custom Renderers. Since Custom Renderers no longer exist in .NET MAUI, Effects are essentially obsolete.

For the .NET MAUI Community Toolkit, we are implementing the functionality of Effects from Xamarin.CommunityToolkit by adding them as Behaviors. @pictos is leading this effort and has documented more information on it in this Proposal: https://github.com/CommunityToolkit/Maui/issues/124

Leveraging .NET MAUI Interfaces

I recommend leveraging the .NET MAUI interfaces to implement/enable this AutoComplete feature for all IText elements.

This will ensure that all future/new IText elements as well as all custom-made IText elements will be able to leverage this feature.

Next Steps

I recommend opening a Discussion to chat about the API Surface and implementation for this feature. This is the best way to avoid additional rework in the future for this Proposal.

coen22 commented 2 years ago

@brminnick thank you for your feedback. I'll create a discussion.

CorentinSwiss4 commented 1 year ago

@coen22 can you link the discussion if you created it ? I'm interesting to propose on my app autofill hints one time code with sms otp. Until now i don't find any working solutions.

Thx

coen22 commented 1 year ago

@coen22 can you link the discussion if you created it ? I'm interesting to propose on my app autofill hints one time code with sms otp. Until now i don't find any working solutions.

Thx

Here you go https://github.com/CommunityToolkit/Maui/discussions/374

In your XAML just add Entry.Behaviours Although I didn’t implement SMS OTP, you can find this page as a reference on Android https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE()

danielheddelin commented 2 weeks ago

I wrote a little blog post showing how you can get OTP working on iOS: https://danielheddelin.blogspot.com/2024/06/otp-auto-fill-entry-for-ios-with-net.html