1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-03 09:03:35 +00:00

PM-3349 MAUI Migration Single Project - Android - Handlers and some fixes to avoid crashing

This commit is contained in:
Federico Maccaroni
2023-08-30 23:37:10 -03:00
parent 6ff978cef2
commit 16675c2a66
38 changed files with 557 additions and 64 deletions

View File

@@ -18,6 +18,7 @@ using Bit.Core.Utilities;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Bit.App.Handlers;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
@@ -51,6 +52,7 @@ namespace Bit.App
public App(AppOptions appOptions)
{
App.SetupHandlers();
Options = appOptions ?? new AppOptions();
if (Options.IosExtension)
{
@@ -200,6 +202,21 @@ namespace Bit.App
});
}
private static void SetupHandlers()
{
new EntryHandlerMappings().Setup();
new EditorHandlerMappings().Setup();
new LabelHandlerMappings().Setup();
new PickerHandlerMappings().Setup();
new SearchBarHandlerMappings().Setup();
new SwitchHandlerMappings().Setup();
new DatePickerHandlerMappings().Setup();
new SliderHandlerMappings().Setup();
new StepperHandlerMappings().Setup();
new TimePickerHandlerMappings().Setup();
new ButtonHandlerMappings().Setup();
}
private async Task CheckPasswordlessLoginRequestsAsync()
{
if (!_isResumed)

View File

@@ -304,6 +304,8 @@
<None Remove="Resources\Fonts\bwi-font.ttf" />
<None Remove="Resources\Fonts\MaterialIcons_Regular.ttf" />
<None Remove="Resources\Fonts\RobotoMono_Regular.ttf" />
<None Remove="Handlers\" />
<None Remove="Platforms\Android\Handlers\" />
</ItemGroup>
<ItemGroup>
<Folder Include="Core\" />
@@ -316,6 +318,8 @@
<Folder Include="Core\Services\" />
<Folder Include="Core\Utilities\" />
<Folder Include="Resources\Fonts\" />
<Folder Include="Handlers\" />
<Folder Include="Platforms\Android\Handlers\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Core\Resources\eff_long_word_list.txt" />

View File

@@ -1,8 +1,4 @@
using System;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Controls
namespace Bit.App.Controls
{
public class HybridWebView : View
{

View File

@@ -1,6 +1,4 @@
using Bit.App.Effects;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Controls
{
@@ -9,17 +7,7 @@ namespace Bit.App.Controls
public IconButton()
{
Padding = 0;
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
switch (Device.RuntimePlatform)
{
case Device.iOS:
FontFamily = "bwi-font";
break;
case Device.Android:
FontFamily = "bwi-font";
break;
}
FontFamily = "bwi-font";
Effects.Add(new RemoveFontPaddingEffect());
}
}

View File

@@ -1,6 +1,4 @@
using Bit.App.Effects;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Controls
{
@@ -10,17 +8,7 @@ namespace Bit.App.Controls
public IconLabel()
{
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
switch (Device.RuntimePlatform)
{
case Device.iOS:
FontFamily = "bwi-font";
break;
case Device.Android:
FontFamily = "bwi-font";
break;
}
FontFamily = "bwi-font";
Effects.Add(new RemoveFontPaddingEffect());
}
}

View File

@@ -1,23 +1,15 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui;
namespace Bit.App.Controls
namespace Bit.App.Controls
{
public class MiButton : Button
{
public MiButton()
{
Padding = 0;
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
switch (Device.RuntimePlatform)
{
case Device.iOS:
FontFamily = "Material Icons";
break;
case Device.Android:
FontFamily = "MaterialIcons_Regular";
break;
}
#if ANDROID
FontFamily = "MaterialIcons_Regular";
#else
FontFamily = "Material Icons";
#endif
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Bit.App.Effects
{
if (Element is Label label && Control is TextView textView)
{
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
textView.SetTextSize(Android.Util.ComplexUnitType.Sp, (float)label.FontSize);
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class ButtonHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class DatePickerHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,10 @@
namespace Bit.App.Handlers
{
public partial class EditorHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class EntryHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class LabelHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class PickerHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,10 @@
namespace Bit.App.Handlers
{
public partial class SearchBarHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class SliderHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,10 @@
namespace Bit.App.Handlers
{
public partial class StepperHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class SwitchHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.App.Handlers
{
public partial class TimePickerHandlerMappings
{
public void Setup() => SetupPlatform();
partial void SetupPlatform();
}
}

View File

@@ -19,8 +19,8 @@ namespace Bit.App.Pages
public Func<Task> ExecuteAsync { get; set; }
public bool SubLabelTextEnabled => SubLabel == AppResources.On;
public string LineBreakMode => SubLabel == null ? "TailTruncation" : "";
public bool ShowSubLabel => SubLabel.Length != 0;
public LineBreakMode LineBreakMode => SubLabel == null ? LineBreakMode.TailTruncation : LineBreakMode.NoWrap;
public bool ShowSubLabel => SubLabel != null && SubLabel.Length != 0;
public bool ShowTimeInput => Time != null;
public Color SubLabelColor => SubLabelTextEnabled ?
ThemeManager.GetResourceColor("SuccessColor") :

View File

@@ -132,7 +132,7 @@
AutomationProperties.Name="{u:I18n Name}"
AutomationId="ItemNameEntry" />
</StackLayout>
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
<VerticalStackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
<Grid StyleClass="box-row, box-row-input"
RowDefinitions="Auto,*"
ColumnDefinitions="*,Auto">
@@ -308,10 +308,11 @@
AutomationProperties.Name="{u:I18n ScanQrTitle}"
/>
</Grid>
</StackLayout>
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
</VerticalStackLayout>
<VerticalStackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
<StackLayout StyleClass="box-row, box-row-input">
<Label
IsVisible="{Binding IsLogin, Mode=OneWay}"
Text="{u:I18n CardholderName}"
StyleClass="box-label" />
<Entry
@@ -430,7 +431,7 @@
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationId="CardShowSecurityCodeButton" />
</Grid>
</StackLayout>
</VerticalStackLayout>
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
<StackLayout StyleClass="box-row, box-row-input">
<Label
@@ -831,7 +832,7 @@
StyleClass="box-label" />
<Picker
x:Name="_ownershipPicker"
ItemsSource="{Binding OwnershipOptions, Mode=OneTime}"
ItemsSource="{Binding OwnershipOptions, Mode=OneWay}"
SelectedIndex="{Binding OwnershipSelectedIndex}"
StyleClass="box-value"
AutomationId="ItemOwnershipPicker" />

View File

@@ -51,8 +51,8 @@ namespace Bit.App.Pages
protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
{
nameof(IsLogin),
nameof(IsIdentity),
nameof(IsLogin),
nameof(IsCard),
nameof(IsSecureNote),
nameof(ShowUris),
@@ -287,7 +287,7 @@ namespace Bit.App.Pages
nameof(ShowCollections)
});
}
public bool ShowCollections => (!EditMode || CloneMode) && Cipher.OrganizationId != null;
public bool ShowCollections => (!EditMode || CloneMode) && Cipher?.OrganizationId != null;
public bool EditMode => !string.IsNullOrWhiteSpace(CipherId);
public bool ShowOwnershipOptions => !EditMode || CloneMode;
public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal;
@@ -298,15 +298,15 @@ namespace Bit.App.Pages
public bool IsCard => Cipher?.Type == CipherType.Card;
public bool IsSecureNote => Cipher?.Type == CipherType.SecureNote;
public bool IsFido2Key => Cipher?.Type == CipherType.Fido2Key;
public bool ShowUris => IsLogin && Cipher.Login.HasUris;
public bool ShowAttachments => Cipher.HasAttachments;
public bool ShowUris => IsLogin && Cipher != null && Cipher.Login.HasUris;
public bool ShowAttachments => Cipher != null && Cipher.HasAttachments;
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardNumberIcon => ShowCardNumber ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string ShowCardCodeIcon => ShowCardCode ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public int PasswordFieldColSpan => Cipher.ViewPassword ? 1 : 4;
public int TotpColumnSpan => Cipher.ViewPassword ? 1 : 2;
public int PasswordFieldColSpan => Cipher != null && Cipher.ViewPassword ? 1 : 4;
public int TotpColumnSpan => Cipher != null && Cipher.ViewPassword ? 1 : 2;
public bool AllowPersonal { get; set; }
public bool PasswordPrompt => Cipher.Reprompt != CipherRepromptType.None;
public bool PasswordPrompt => Cipher != null && Cipher.Reprompt != CipherRepromptType.None;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
@@ -459,6 +459,12 @@ namespace Bit.App.Pages
}
_previousCipherId = CipherId;
MainThread.BeginInvokeOnMainThread(() =>
{
TriggerPropertyChanged(nameof(OwnershipOptions));
TriggerPropertyChanged(nameof(OwnershipSelectedIndex));
});
return true;
}
@@ -892,7 +898,7 @@ namespace Bit.App.Pages
private void TriggerCipherChanged()
{
TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged);
MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(Cipher), AdditionalPropertiesToRaiseOnCipherChanged));
}
private async Task CopyTotpClipboardAsync()

View File

@@ -0,0 +1,18 @@
using Bit.App.Controls;
namespace Bit.App.Handlers
{
public partial class ButtonHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.ButtonHandler.Mapper.AppendToMapping("CustomButtonHandler", (handler, button) =>
{
if (button is IconButton || button is MiButton)
{
handler.PlatformView.SetBackgroundResource(0);
}
});
}
}
}

View File

@@ -0,0 +1,39 @@
using Android.Views;
using Bit.App.Controls;
using Microsoft.Maui.Handlers;
namespace Bit.App.Handlers
{
public partial class DatePickerHandlerMappings
{
partial void SetupPlatform()
{
DatePickerHandler.Mapper.AppendToMapping("CustomDatePickerHandler", (handler, datePicker) =>
{
if (datePicker is ExtendedDatePicker extDatePicker)
{
// center text
handler.PlatformView.Gravity = GravityFlags.CenterHorizontal;
// use placeholder until NullableDate set
if (!extDatePicker.NullableDate.HasValue)
{
handler.PlatformView.Text = extDatePicker.PlaceHolder;
}
}
});
DatePickerHandler.Mapper.AppendToMapping(nameof(IDatePicker.Date), UpdateTextPlaceholderOnFormatLikePlacholder);
DatePickerHandler.Mapper.AppendToMapping(nameof(IDatePicker.Format), UpdateTextPlaceholderOnFormatLikePlacholder);
}
private void UpdateTextPlaceholderOnFormatLikePlacholder(IDatePickerHandler handler, IDatePicker datePicker)
{
if (datePicker is ExtendedDatePicker extDatePicker && extDatePicker.Format == extDatePicker.PlaceHolder)
{
handler.PlatformView.Text = extDatePicker.PlaceHolder;
}
}
}
}

View File

@@ -0,0 +1,24 @@
using Android.Views.InputMethods;
using Bit.App.Droid.Utilities;
namespace Bit.App.Handlers
{
public partial class EditorHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("CustomEditorHandler", (handler, editor) =>
{
handler.PlatformView.SetPadding(handler.PlatformView.PaddingLeft, handler.PlatformView.PaddingTop - 10, handler.PlatformView.PaddingRight,
handler.PlatformView.PaddingBottom + 20);
handler.PlatformView.ImeOptions = handler.PlatformView.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
(ImeAction)ImeFlags.NoExtractUi;
});
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(nameof(IEditor.TextColor), (handler, editor) =>
{
handler.PlatformView.BackgroundTintList = ThemeHelpers.GetStateFocusedColors();
});
}
}
}

View File

@@ -0,0 +1,63 @@
using Android.Graphics;
using Android.Text;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.App.Droid.Utilities;
using Microsoft.Maui.Platform;
namespace Bit.App.Handlers
{
public partial class EntryHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("CustomEntryHandler", (handler, entry) =>
{
handler.PlatformView.SetPadding(handler.PlatformView.PaddingLeft, handler.PlatformView.PaddingTop - 10, handler.PlatformView.PaddingRight,
handler.PlatformView.PaddingBottom + 20);
handler.PlatformView.ImeOptions = handler.PlatformView.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
(ImeAction)ImeFlags.NoExtractUi;
});
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(nameof(IEntry.TextColor), (handler, entry) =>
{
handler.PlatformView.BackgroundTintList = ThemeHelpers.GetStateFocusedColors();
});
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(nameof(IEntry.IsPassword), (handler, entry) =>
{
// Check if field type is text, otherwise ignore (numeric passwords, etc.)
handler.PlatformView.InputType = entry.Keyboard.ToInputType();
bool isText = (handler.PlatformView.InputType & InputTypes.ClassText) == InputTypes.ClassText,
isNumber = (handler.PlatformView.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber;
if (!isText && !isNumber)
{
return;
}
if (entry.IsPassword)
{
// Element is a password field, set inputType to TextVariationPassword which disables
// predictive text by default
handler.PlatformView.InputType = handler.PlatformView.InputType |
(isText ? InputTypes.TextVariationPassword : InputTypes.NumberVariationPassword);
}
else
{
// Element is not a password field, set inputType to TextVariationVisiblePassword to
// disable predictive text while still displaying the content.
handler.PlatformView.InputType = handler.PlatformView.InputType |
(isText ? InputTypes.TextVariationVisiblePassword : InputTypes.NumberVariationNormal);
}
// The workaround above forces a reset of the style properties, so we need to re-apply the font.
// see https://xamarin.github.io/bugzilla-archives/33/33666/bug.html
var typeface = Typeface.CreateFromAsset(handler.PlatformView.Context.Assets, "RobotoMono_Regular.ttf");
if (handler.PlatformView is TextView label)
{
label.Typeface = typeface;
}
});
}
}
}

View File

@@ -0,0 +1,31 @@
using Android.OS;
using Bit.App.Controls;
namespace Bit.App.Handlers
{
public partial class LabelHandlerMappings
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.LabelHandler.Mapper.AppendToMapping("CustomLabelHandler", (handler, label) =>
{
if (label is CustomLabel customLabel && customLabel.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
handler.PlatformView.Typeface = Android.Graphics.Typeface.Create(null, customLabel.FontWeight.Value, false);
return;
}
if (label is SelectableLabel)
{
handler.PlatformView.SetTextIsSelectable(true);
}
});
Microsoft.Maui.Handlers.LabelHandler.Mapper.AppendToMapping(nameof(ILabel.AutomationId), (handler, label) =>
{
handler.PlatformView.ContentDescription = label.AutomationId;
});
}
}
}

View File

@@ -0,0 +1,21 @@
using Bit.App.Droid.Utilities;
namespace Bit.App.Handlers
{
public partial class PickerHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping("CustomPickerHandler", (handler, picker) =>
{
handler.PlatformView.SetPadding(handler.PlatformView.PaddingLeft, handler.PlatformView.PaddingTop - 10,
handler.PlatformView.PaddingRight, handler.PlatformView.PaddingBottom + 20);
});
Microsoft.Maui.Handlers.PickerHandler.Mapper.AppendToMapping(nameof(IPicker.TextColor), (handler, picker) =>
{
handler.PlatformView.BackgroundTintList = ThemeHelpers.GetStateFocusedColors();
});
}
}
}

View File

@@ -0,0 +1,26 @@
using Android.Views.InputMethods;
namespace Bit.App.Handlers
{
public partial class SearchBarHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.SearchBarHandler.Mapper.AppendToMapping("CustomSearchBarHandler", (handler, searchBar) =>
{
try
{
var magId = handler.PlatformView.Resources.GetIdentifier("android:id/search_mag_icon", null, null);
var magImage = (Android.Widget.ImageView)handler.PlatformView.FindViewById(magId);
magImage.LayoutParameters = new Android.Widget.LinearLayout.LayoutParams(0, 0);
}
catch { }
// TODO: [MAUI-Migration] [Check]
handler.PlatformView.ImeOptions = handler.PlatformView.ImeOptions | (int)ImeFlags.NoPersonalizedLearning |
(int)ImeFlags.NoExtractUi;
//Control.SetImeOptions(Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
// (ImeAction)ImeFlags.NoExtractUi);
});
}
}
}

View File

@@ -0,0 +1,32 @@
using Android.Graphics.Drawables;
using AndroidX.Core.Content.Resources;
using Bit.App.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
namespace Bit.App.Handlers
{
public partial class SliderHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.SliderHandler.Mapper.AppendToMapping(nameof(ExtendedSlider.ThumbBorderColor), (handler, slider) =>
{
var t = ResourcesCompat.GetDrawable(handler.PlatformView.Resources, Resource.Drawable.slider_thumb, null);
if (t is GradientDrawable thumb && slider is ExtendedSlider extSlider)
{
// TODO: [MAUI-Migration]
//if (view.ThumbColor == Colors.Default)
//{
// thumb.SetColor(Colors.White.ToAndroid());
//}
//else
//{
thumb.SetColor(extSlider.ThumbColor.ToAndroid());
//}
thumb.SetStroke(3, extSlider.ThumbBorderColor.ToAndroid());
handler.PlatformView.SetThumb(thumb);
}
});
}
}
}

View File

@@ -0,0 +1,45 @@
using Android.Graphics;
using Android.OS;
using Bit.App.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
namespace Bit.App.Handlers
{
public partial class StepperHandlerMappings
{
partial void SetupPlatform()
{
Microsoft.Maui.Handlers.StepperHandler.Mapper.AppendToMapping(nameof(ExtendedStepper.StepperBackgroundColor), (handler, stepper) =>
{
if (stepper is ExtendedStepper extStepper)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
handler.PlatformView.GetChildAt(0)?.Background?.SetColorFilter(
new BlendModeColorFilter(extStepper.StepperBackgroundColor.ToAndroid(), Android.Graphics.BlendMode.Multiply));
handler.PlatformView.GetChildAt(1)?.Background?.SetColorFilter(
new BlendModeColorFilter(extStepper.StepperBackgroundColor.ToAndroid(), Android.Graphics.BlendMode.Multiply));
}
else
{
handler.PlatformView.GetChildAt(0)?.Background?.SetColorFilter(
extStepper.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
handler.PlatformView.GetChildAt(1)?.Background?.SetColorFilter(
extStepper.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
}
}
});
Microsoft.Maui.Handlers.StepperHandler.Mapper.AppendToMapping(nameof(ExtendedStepper.StepperForegroundColor), (handler, stepper) =>
{
if (stepper is ExtendedStepper extStepper)
{
var btn0 = handler.PlatformView.GetChildAt(0) as Android.Widget.Button;
btn0?.SetTextColor(extStepper.StepperForegroundColor.ToAndroid());
var btn1 = handler.PlatformView.GetChildAt(1) as Android.Widget.Button;
btn1?.SetTextColor(extStepper.StepperForegroundColor.ToAndroid());
}
});
}
}
}

View File

@@ -0,0 +1,42 @@
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.OS;
using AndroidX.Core.Content.Resources;
using Bit.App.Droid.Utilities;
namespace Bit.App.Handlers
{
public partial class SwitchHandlerMappings
{
partial void SetupPlatform()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
{
// Android 5.x doesn't support ThumbTintList, and using SwitchCompat on every version after 5.x
// doesn't apply tinting the way we want. Let 5.x to do its own thing here.
return;
}
Microsoft.Maui.Handlers.SwitchHandler.Mapper.AppendToMapping(nameof(ISwitch.ThumbColor), (handler, mauiSwitch) =>
{
handler.PlatformView.SetHintTextColor(ThemeHelpers.MutedColor);
var t = ResourcesCompat.GetDrawable(handler.PlatformView.Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb)
{
handler.PlatformView.ThumbDrawable = thumb;
}
var thumbStates = new[]
{
new[] { Android.Resource.Attribute.StateChecked }, // checked
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
};
var thumbColors = new int[]
{
ThemeHelpers.SwitchOnColor,
ThemeHelpers.SwitchThumbColor
};
handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
});
}
}
}

View File

@@ -0,0 +1,39 @@
using Android.Views;
using Bit.App.Controls;
using Microsoft.Maui.Handlers;
namespace Bit.App.Handlers
{
public partial class TimePickerHandlerMappings
{
partial void SetupPlatform()
{
TimePickerHandler.Mapper.AppendToMapping("CustomTimePickerHandler", (handler, timePicker) =>
{
if (timePicker is ExtendedTimePicker extTimePicker)
{
// center text
handler.PlatformView.Gravity = GravityFlags.CenterHorizontal;
// use placeholder until NullableTime set
if (!extTimePicker.NullableTime.HasValue)
{
handler.PlatformView.Text = extTimePicker.PlaceHolder;
}
}
});
TimePickerHandler.Mapper.AppendToMapping(nameof(ITimePicker.Time), UpdateTextPlaceholderOnFormatLikePlacholder);
TimePickerHandler.Mapper.AppendToMapping(nameof(ITimePicker.Format), UpdateTextPlaceholderOnFormatLikePlacholder);
}
private void UpdateTextPlaceholderOnFormatLikePlacholder(ITimePickerHandler handler, ITimePicker timePicker)
{
if (timePicker is ExtendedTimePicker extDatePicker && extDatePicker.Format == extDatePicker.PlaceHolder)
{
handler.PlatformView.Text = extDatePicker.PlaceHolder;
}
}
}
}

View File

@@ -10,6 +10,7 @@ using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
// TODO: Convert to handler
public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener
{
private TabbedPage _page;

View File

@@ -6,6 +6,7 @@ using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
// TODO: Convert to handler
public class ExtendedGridRenderer : ViewRenderer
{
public ExtendedGridRenderer(Context context) : base(context) { }

View File

@@ -6,6 +6,7 @@ using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
// TODO: Convert to handler
public class ExtendedStackLayoutRenderer : ViewRenderer
{
public ExtendedStackLayoutRenderer(Context context) : base(context) { }

View File

@@ -7,7 +7,7 @@
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
</style>
<style name="BaseTheme" parent="Theme.AppCompat">
<style name="BaseTheme" parent="Theme.MaterialComponents">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="colorPrimaryDark">@color/dark_notificationBar</item>

View File

@@ -1,4 +1,5 @@
using Android.Graphics;
using Android.Content.Res;
using Android.Graphics;
using Bit.App.Utilities;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Color = Android.Graphics.Color;
@@ -72,5 +73,20 @@ namespace Bit.App.Droid.Utilities
LightTheme = true;
}
}
internal static ColorStateList GetStateFocusedColors()
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
PrimaryColor,
MutedColor
};
return new ColorStateList(states, colors);
}
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Styles.Android"