1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

MAUI Migration Single Project - Android - Fonts, renderers, effects

This commit is contained in:
Federico Maccaroni
2023-07-31 22:15:14 -03:00
parent 276081f5f7
commit 6ff978cef2
44 changed files with 1046 additions and 185 deletions

View File

@@ -45,10 +45,8 @@
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
<MauiFont Include="Resources\Fonts\*" />
</ItemGroup>
<ItemGroup>
@@ -302,6 +300,10 @@
<None Remove="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
<None Remove="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
<None Remove="Platforms\Android\google-services.json" />
<None Remove="Resources\Fonts\" />
<None Remove="Resources\Fonts\bwi-font.ttf" />
<None Remove="Resources\Fonts\MaterialIcons_Regular.ttf" />
<None Remove="Resources\Fonts\RobotoMono_Regular.ttf" />
</ItemGroup>
<ItemGroup>
<Folder Include="Core\" />
@@ -313,6 +315,7 @@
<Folder Include="Core\Resources\" />
<Folder Include="Core\Services\" />
<Folder Include="Core\Utilities\" />
<Folder Include="Resources\Fonts\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Core\Resources\eff_long_word_list.txt" />

View File

@@ -16,7 +16,7 @@ namespace Bit.App.Controls
FontFamily = "bwi-font";
break;
case Device.Android:
FontFamily = "bwi-font.ttf#bwi-font";
FontFamily = "bwi-font";
break;
}

View File

@@ -17,7 +17,7 @@ namespace Bit.App.Controls
FontFamily = "bwi-font";
break;
case Device.Android:
FontFamily = "bwi-font.ttf#bwi-font";
FontFamily = "bwi-font";
break;
}

View File

@@ -15,7 +15,7 @@ namespace Bit.App.Controls
FontFamily = "Material Icons";
break;
case Device.Android:
FontFamily = "MaterialIcons_Regular.ttf#Material Icons";
FontFamily = "MaterialIcons_Regular";
break;
}
}

View File

@@ -14,7 +14,7 @@ namespace Bit.App.Controls
FontFamily = "Material Icons";
break;
case Device.Android:
FontFamily = "MaterialIcons_Regular.ttf#Material Icons";
FontFamily = "MaterialIcons_Regular";
break;
}
}

View File

@@ -7,16 +7,11 @@ namespace Bit.App.Controls
{
public MonoEntry()
{
// 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 = "Menlo-Regular";
break;
case Device.Android:
FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
break;
}
#if ANDROID
FontFamily = "RobotoMono_Regular";
#elif IOS
FontFamily = "Menlo-Regular";
#endif
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Bit.App.Controls
FontFamily = "Menlo-Regular";
break;
case Device.Android:
FontFamily = "RobotoMono_Regular.ttf#Roboto Mono";
FontFamily = "RobotoMono_Regular";
break;
}
}

View File

@@ -1,12 +1,38 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Microsoft.Maui.Controls.Platform;
#if ANDROID
using Android.Graphics.Drawables;
using Bit.App.Droid.Utilities;
#endif
namespace Bit.App.Effects
{
public class FabShadowEffect : RoutingEffect
{
public FabShadowEffect()
: base("Bitwarden.FabShadowEffect")
{ }
}
#if ANDROID
public class FabShadowPlatformEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is Android.Widget.Button button)
{
var gd = new GradientDrawable();
gd.SetColor(ThemeHelpers.FabColor);
gd.SetCornerRadius(100);
button.SetBackground(gd);
button.Elevation = 6;
button.TranslationZ = 20;
}
}
protected override void OnDetached()
{
}
}
#endif
}

View File

@@ -1,12 +1,32 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Microsoft.Maui.Controls.Platform;
#if ANDROID
using Android.Widget;
#endif
namespace Bit.App.Effects
{
public class FixedSizeEffect : RoutingEffect
{
public FixedSizeEffect()
: base("Bitwarden.FixedSizeEffect")
{ }
}
#if ANDROID
public class FixedSizePlatformEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Element is Label label && Control is TextView textView)
{
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
}
}
protected override void OnDetached()
{
}
}
#endif
}

View File

@@ -1,13 +1,32 @@
using System;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Microsoft.Maui.Controls.Platform;
#if ANDROID
using Android.Widget;
#endif
namespace Bit.App.Effects
{
public class NoEmojiKeyboardEffect : RoutingEffect
{
public NoEmojiKeyboardEffect()
: base("Bitwarden.NoEmojiKeyboardEffect")
{ }
}
#if ANDROID
public class NoEmojiKeyboardPlatformEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is EditText editText)
{
editText.InputType = Android.Text.InputTypes.ClassText | Android.Text.InputTypes.TextVariationVisiblePassword | Android.Text.InputTypes.TextFlagMultiLine;
}
}
protected override void OnDetached()
{
}
}
#endif
}

View File

@@ -1,14 +1,32 @@
using System;
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Microsoft.Maui.Controls.Platform;
#if ANDROID
using Android.Widget;
#endif
namespace Bit.App.Effects
{
public class RemoveFontPaddingEffect : RoutingEffect
{
public RemoveFontPaddingEffect()
: base("Bitwarden.RemoveFontPaddingEffect")
{ }
}
#if ANDROID
public class RemoveFontPaddingPlatformEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is TextView textView)
{
textView.SetIncludeFontPadding(false);
}
}
protected override void OnDetached()
{
}
}
#endif
}

View File

@@ -1,12 +1,34 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Effects
{
public class TabBarEffect : RoutingEffect
{
public TabBarEffect()
: base("Bitwarden.TabBarEffect")
{ }
}
#if ANDROID
public class TabBarPlatformEffect : PlatformEffect
{
protected override void OnAttached()
{
// TODO: [MAUI-Migration] [Critical]
// now Container is View instead of ViewGroup, let's review this
//if (!(Container.GetChildAt(0) is ViewGroup layout))
//{
// return;
//}
//if (!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
//{
// return;
//}
//bottomNavigationView.LabelVisibilityMode = LabelVisibilityMode.LabelVisibilityLabeled;
}
protected override void OnDetached()
{
}
}
#endif
}

View File

@@ -1,4 +1,5 @@
using System;
using Bit.App.Controls;
using CommunityToolkit.Maui;
using FFImageLoading.Maui;
using Microsoft.Extensions.Logging;
@@ -13,7 +14,7 @@ namespace Bit.App;
public static class MauiProgram
{
public static MauiApp CreateMauiApp(Action<IEffectsBuilder> effectsBuilder)
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
@@ -23,12 +24,47 @@ public static class MauiProgram
.UseBarcodeReader()
.UseSkiaSharp()
.UseFFImageLoading()
.ConfigureEffects(effectsBuilder)
.ConfigureEffects(effects =>
{
#if ANDROID
effects.Add<Effects.FabShadowEffect, Effects.FabShadowPlatformEffect>();
effects.Add<Effects.FixedSizeEffect, Effects.FixedSizePlatformEffect>();
effects.Add<Effects.NoEmojiKeyboardEffect, Effects.NoEmojiKeyboardPlatformEffect>();
effects.Add<Effects.TabBarEffect, Effects.TabBarPlatformEffect>();
effects.Add<Effects.RemoveFontPaddingEffect, Effects.RemoveFontPaddingPlatformEffect>();
#endif
})
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("RobotoMono_Regular.ttf#Roboto Mono", "RobotoMono_Regular");
fonts.AddFont("bwi-font.ttf#bwi-font", "bwi-font");
fonts.AddFont("MaterialIcons_Regular.ttf#Material Icons", "MaterialIcons_Regular");
});
// TODO: [MAUI-Migration] Convert renderers to handlers
// Currently, there's an issue on reusing renderers https://github.com/dotnet/maui/issues/9936
// .ConfigureMauiHandlers(handlers =>
// {
//#if ANDROID
// handlers.AddHandler(typeof(Editor), typeof(Droid.Renderers.CustomEditorRenderer));
// handlers.AddHandler(typeof(Entry), typeof(Droid.Renderers.CustomEntryRenderer));
// handlers.AddHandler(typeof(CustomLabel), typeof(Droid.Renderers.CustomLabelRenderer));
// //handlers.AddHandler(typeof(ContentPage), typeof(Droid.Renderers.CustomPageRenderer));
// handlers.AddHandler(typeof(Picker), typeof(Droid.Renderers.CustomPickerRenderer));
// handlers.AddHandler(typeof(SearchBar), typeof(Droid.Renderers.CustomSearchBarRenderer));
// handlers.AddHandler(typeof(Switch), typeof(Droid.Renderers.CustomSwitchRenderer));
// handlers.AddHandler(typeof(TabbedPage), typeof(Droid.Renderers.CustomTabbedRenderer));
// handlers.AddHandler(typeof(ExtendedDatePicker), typeof(Droid.Renderers.ExtendedDatePickerRenderer));
// handlers.AddHandler(typeof(ExtendedGrid), typeof(Droid.Renderers.ExtendedGridRenderer));
// handlers.AddHandler(typeof(ExtendedSlider), typeof(Droid.Renderers.ExtendedSliderRenderer));
// handlers.AddHandler(typeof(ExtendedStackLayout), typeof(Droid.Renderers.ExtendedStackLayoutRenderer));
// handlers.AddHandler(typeof(ExtendedStepper), typeof(Droid.Renderers.ExtendedStepperRenderer));
// handlers.AddHandler(typeof(ExtendedTimePicker), typeof(Droid.Renderers.ExtendedTimePickerRenderer));
// handlers.AddHandler(typeof(HybridWebView), typeof(Droid.Renderers.HybridWebViewRenderer));
// handlers.AddHandler(typeof(SelectableLabel), typeof(Droid.Renderers.SelectableLabelRenderer));
//#elif IOS
// // TODO: configure
//#endif
// });
#if DEBUG
builder.Logging.AddDebug();

View File

@@ -199,11 +199,14 @@ namespace Bit.App.Pages
Text = string.Format("{0}:", AppResources.PasswordHistory),
FontAttributes = FontAttributes.Bold
});
fs.Spans.Add(new Span
if (Cipher?.PasswordHistory != null)
{
Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()),
TextColor = ThemeManager.GetResourceColor("PrimaryColor")
});
fs.Spans.Add(new Span
{
Text = string.Format(" {0}", Cipher.PasswordHistory.Count.ToString()),
TextColor = ThemeManager.GetResourceColor("PrimaryColor")
});
}
return fs;
}
}

View File

@@ -90,7 +90,7 @@ namespace Bit.App.Pages
base.OnAppearing();
if (_syncService.SyncInProgress)
{
IsBusy = true;
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
}
_accountAvatar?.OnAppearing();
@@ -105,7 +105,7 @@ namespace Bit.App.Pages
{
if (message.Command == "syncStarted")
{
Device.BeginInvokeOnMainThread(() => IsBusy = true);
MainThread.BeginInvokeOnMainThread(() => IsBusy = true);
}
else if (message.Command == "syncCompleted")
{
@@ -114,7 +114,7 @@ namespace Bit.App.Pages
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
Device.BeginInvokeOnMainThread(() =>
MainThread.BeginInvokeOnMainThread(() =>
{
IsBusy = false;
if (_vm.LoadedOnce)

View File

@@ -1,29 +0,0 @@
using Android.Graphics.Drawables;
using Bit.App.Droid.Effects;
using Bit.App.Droid.Utilities;
using Microsoft.Maui.Controls.Platform;
[assembly: ExportEffect(typeof(FabShadowEffect), "FabShadowEffect")]
namespace Bit.App.Droid.Effects
{
public class FabShadowEffect : PlatformEffect
{
protected override void OnAttached ()
{
if (Control is Android.Widget.Button button)
{
var gd = new GradientDrawable();
gd.SetColor(ThemeHelpers.FabColor);
gd.SetCornerRadius(100);
button.SetBackground(gd);
button.Elevation = 6;
button.TranslationZ = 20;
}
}
protected override void OnDetached ()
{
}
}
}

View File

@@ -1,22 +0,0 @@
using Android.Widget;
using Bit.App.Droid.Effects;
using Microsoft.Maui.Controls.Platform;
[assembly: ExportEffect(typeof(FixedSizeEffect), "FixedSizeEffect")]
namespace Bit.App.Droid.Effects
{
public class FixedSizeEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Element is Label label && Control is TextView textView)
{
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -1,23 +0,0 @@
using Android.Widget;
using Bit.App.Droid.Effects;
using Microsoft.Maui.Controls.Platform;
[assembly: ExportEffect(typeof(NoEmojiKeyboardEffect), nameof(NoEmojiKeyboardEffect))]
namespace Bit.App.Droid.Effects
{
public class NoEmojiKeyboardEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is EditText editText)
{
editText.InputType = Android.Text.InputTypes.ClassText | Android.Text.InputTypes.TextVariationVisiblePassword | Android.Text.InputTypes.TextFlagMultiLine;
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -1,22 +0,0 @@
using Android.Widget;
using Bit.App.Droid.Effects;
using Microsoft.Maui.Controls.Platform;
[assembly: ExportEffect(typeof(RemoveFontPaddingEffect), nameof(RemoveFontPaddingEffect))]
namespace Bit.App.Droid.Effects
{
public class RemoveFontPaddingEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is TextView textView)
{
textView.SetIncludeFontPadding(false);
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -1,32 +0,0 @@
using Android.Views;
using Bit.App.Droid.Effects;
using Google.Android.Material.BottomNavigation;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Platform;
[assembly: ResolutionGroupName("Bitwarden")]
[assembly: ExportEffect(typeof(TabBarEffect), "TabBarEffect")]
namespace Bit.App.Droid.Effects
{
public class TabBarEffect : PlatformEffect
{
protected override void OnAttached()
{
// TODO: [MAUI-Migration] [Critical]
// now Container is View instead of ViewGroup, let's review this
//if (!(Container.GetChildAt(0) is ViewGroup layout))
//{
// return;
//}
//if (!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
//{
// return;
//}
//bottomNavigationView.LabelVisibilityMode = LabelVisibilityMode.LabelVisibilityLabeled;
}
protected override void OnDetached()
{
}
}
}

View File

@@ -98,16 +98,7 @@ namespace Bit.App.Droid
#endif
}
protected override MauiApp CreateMauiApp()
{
return MauiProgram.CreateMauiApp(effects =>
{
effects.Add<Bit.App.Effects.FabShadowEffect, Droid.Effects.FabShadowEffect>();
effects.Add<Bit.App.Effects.FixedSizeEffect, Droid.Effects.FixedSizeEffect>();
effects.Add<Bit.App.Effects.NoEmojiKeyboardEffect, Droid.Effects.NoEmojiKeyboardEffect>();
effects.Add<Bit.App.Effects.TabBarEffect, Droid.Effects.TabBarEffect>();
});
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
public override void OnCreate()
{

View File

@@ -0,0 +1,67 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Views.InputMethods;
using Bit.App.Droid.Utilities;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomEditorRenderer : EditorRenderer
{
public CustomEditorRenderer(Context context)
: base(context)
{ }
// Workaround for issue described here:
// https://github.com/xamarin/Xamarin.Forms/issues/8291#issuecomment-617456651
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
EditText.Enabled = false;
EditText.Enabled = true;
}
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
(ImeAction)ImeFlags.NoExtractUi;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -0,0 +1,106 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Text;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.App.Droid.Utilities;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomEntryRenderer : EntryRenderer
{
public CustomEntryRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
(ImeAction)ImeFlags.NoExtractUi;
}
}
// Workaround for bug preventing long-press -> copy/paste on Android 11
// See https://issuetracker.google.com/issues/37095917
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Control.Enabled = false;
Control.Enabled = true;
}
// Workaround for failure to disable text prediction on non-password fields
// see https://github.com/xamarin/Xamarin.Forms/issues/10857
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
// Check if changed property is "IsPassword", otherwise ignore
if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
{
// Check if field type is text, otherwise ignore (numeric passwords, etc.)
EditText.InputType = Element.Keyboard.ToInputType();
bool isText = (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText,
isNumber = (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber;
if (isText || isNumber)
{
if (Element.IsPassword)
{
// Element is a password field, set inputType to TextVariationPassword which disables
// predictive text by default
EditText.InputType = EditText.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.
EditText.InputType = EditText.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(Context.Assets, "RobotoMono_Regular.ttf");
if (Control is TextView label)
{
label.Typeface = typeface;
}
}
}
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -0,0 +1,42 @@
using System.ComponentModel;
using Android.Content;
using Android.OS;
using Bit.App.Controls;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomLabelRenderer : LabelRenderer
{
public CustomLabelRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is CustomLabel label)
{
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var label = sender as CustomLabel;
switch (e.PropertyName)
{
case nameof(CustomLabel.AutomationId):
Control.ContentDescription = label.AutomationId;
break;
}
base.OnElementPropertyChanged(sender, e);
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using Android.App;
using Android.Content;
using AndroidX.AppCompat.Widget;
using Bit.App.Resources;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
using Toolbar = AndroidX.AppCompat.Widget.Toolbar;
namespace Bit.App.Droid.Renderers
{
public class CustomPageRenderer : PageRenderer
{
public CustomPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
// TODO: [MAUI-Migration] [Critical]
//Activity context = (Activity)this.Context;
//var toolbar = context.FindViewById<Toolbar>(Android.Resource.Id.toolbar);
//if(toolbar != null)
//{
// toolbar.NavigationContentDescription = AppResources.TapToGoBack;
//}
}
}
}

View File

@@ -0,0 +1,57 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Bit.App.Droid.Utilities;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomPickerRenderer : PickerRenderer
{
public CustomPickerRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
UpdateBorderColor();
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Picker.TextColorProperty.PropertyName)
{
UpdateBorderColor();
}
}
private void UpdateBorderColor()
{
if (Control != null)
{
var states = new[]
{
new[] { Android.Resource.Attribute.StateFocused }, // focused
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
};
var colors = new int[]
{
ThemeHelpers.PrimaryColor,
ThemeHelpers.MutedColor
};
Control.BackgroundTintList = new ColorStateList(states, colors);
}
}
}
}

View File

@@ -0,0 +1,35 @@
using Android.Content;
using Android.Views.InputMethods;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomSearchBarRenderer : SearchBarRenderer
{
public CustomSearchBarRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement != null)
{
try
{
var magId = Resources.GetIdentifier("android:id/search_mag_icon", null, null);
var magImage = (Android.Widget.ImageView)Control.FindViewById(magId);
magImage.LayoutParameters = new Android.Widget.LinearLayout.LayoutParams(0, 0);
}
catch { }
// TODO: [MAUI-Migration] [Check]
Control.ImeOptions = Control.ImeOptions | (int)ImeFlags.NoPersonalizedLearning |
(int)ImeFlags.NoExtractUi;
//Control.SetImeOptions(Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
// (ImeAction)ImeFlags.NoExtractUi);
}
}
}
}

View File

@@ -0,0 +1,66 @@
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.OS;
using AndroidX.Core.Content.Resources;
using Bit.App.Droid.Renderers;
using Bit.App.Droid.Utilities;
using Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
{
base.OnElementChanged(e);
UpdateColors();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Switch.OnColorProperty.PropertyName)
{
UpdateColors();
}
}
private void UpdateColors()
{
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;
}
if (Control != null)
{
Control.SetHintTextColor(ThemeHelpers.MutedColor);
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb)
{
Control.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
};
Control.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
}
}
}
}

View File

@@ -0,0 +1,65 @@
using Android.Content;
using Android.Views;
using Bit.App.Pages;
using Bit.App.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Navigation;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class CustomTabbedRenderer : TabbedPageRenderer, NavigationBarView.IOnItemReselectedListener
{
private TabbedPage _page;
public CustomTabbedRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_page = e.NewElement;
GetBottomNavigationView()?.SetOnItemReselectedListener(this);
}
else
{
_page = e.OldElement;
}
}
private BottomNavigationView GetBottomNavigationView()
{
for (var i = 0; i < ChildCount; i++)
{
var childView = GetChildAt(i);
if (childView is ViewGroup viewGroup)
{
for (var j = 0; j < viewGroup.ChildCount; j++)
{
var childRelativeLayoutView = viewGroup.GetChildAt(j);
if (childRelativeLayoutView is BottomNavigationView bottomNavigationView)
{
return bottomNavigationView;
}
}
}
}
return null;
}
public void OnNavigationItemReselected(IMenuItem item)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
if (_page is TabsPage tabsPage)
{
tabsPage.OnPageReselected();
}
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System.ComponentModel;
using Android.Content;
using Android.Views;
using Bit.App.Controls;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class ExtendedDatePickerRenderer : DatePickerRenderer
{
public ExtendedDatePickerRenderer(Context context)
: base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if (Control != null && Element is ExtendedDatePicker element)
{
// center text
Control.Gravity = GravityFlags.CenterHorizontal;
// use placeholder until NullableDate set
if (!element.NullableDate.HasValue)
{
Control.Text = element.PlaceHolder;
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == DatePicker.DateProperty.PropertyName ||
e.PropertyName == DatePicker.FormatProperty.PropertyName)
{
if (Control != null && Element is ExtendedDatePicker element)
{
if (Element.Format == element.PlaceHolder)
{
Control.Text = element.PlaceHolder;
return;
}
}
}
base.OnElementPropertyChanged(sender, e);
}
}
}

View File

@@ -0,0 +1,22 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class ExtendedGridRenderer : ViewRenderer
{
public ExtendedGridRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
{
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(Resource.Drawable.list_item_bg);
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System.ComponentModel;
using Android.Content;
using Android.Graphics.Drawables;
using AndroidX.Core.Content.Resources;
using Bit.App.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class ExtendedSliderRenderer : SliderRenderer
{
public ExtendedSliderRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
UpdateColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedSlider.ThumbBorderColorProperty.PropertyName)
{
UpdateColor();
}
}
private void UpdateColor()
{
if (Control != null && Element is ExtendedSlider view)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
if (t is GradientDrawable thumb)
{
// TODO: [MAUI-Migration]
//if (view.ThumbColor == Colors.Default)
//{
// thumb.SetColor(Colors.White.ToAndroid());
//}
//else
//{
thumb.SetColor(view.ThumbColor.ToAndroid());
//}
thumb.SetStroke(3, view.ThumbBorderColor.ToAndroid());
Control.SetThumb(thumb);
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
using Android.Content;
using Bit.App.Controls;
using Bit.App.Droid.Renderers;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class ExtendedStackLayoutRenderer : ViewRenderer
{
public ExtendedStackLayoutRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
{
base.OnElementChanged(elementChangedEvent);
if (elementChangedEvent.NewElement != null)
{
SetBackgroundResource(Resource.Drawable.list_item_bg);
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Bit.App.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class ExtendedStepperRenderer : StepperRenderer
{
public ExtendedStepperRenderer(Context context)
: base(context)
{}
protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
{
base.OnElementChanged(e);
UpdateBgColor();
UpdateFgColor();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedStepper.StepperBackgroundColorProperty.PropertyName)
{
UpdateBgColor();
}
else if (e.PropertyName == ExtendedStepper.StepperForegroundColorProperty.PropertyName)
{
UpdateFgColor();
}
}
private void UpdateBgColor()
{
if (Control != null && Element is ExtendedStepper view)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
Control.GetChildAt(0)?.Background?.SetColorFilter(
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), Android.Graphics.BlendMode.Multiply));
Control.GetChildAt(1)?.Background?.SetColorFilter(
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), Android.Graphics.BlendMode.Multiply));
}
else
{
Control.GetChildAt(0)?.Background?.SetColorFilter(
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
Control.GetChildAt(1)?.Background?.SetColorFilter(
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
}
}
}
private void UpdateFgColor()
{
if (Control != null && Element is ExtendedStepper view)
{
var btn0 = Control.GetChildAt(0) as Android.Widget.Button;
btn0?.SetTextColor(view.StepperForegroundColor.ToAndroid());
var btn1 = Control.GetChildAt(1) as Android.Widget.Button;
btn1?.SetTextColor(view.StepperForegroundColor.ToAndroid());
}
}
}
}

View File

@@ -0,0 +1,48 @@
using System.ComponentModel;
using Android.Content;
using Android.Views;
using Bit.App.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class ExtendedTimePickerRenderer : TimePickerRenderer
{
public ExtendedTimePickerRenderer(Context context)
: base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
{
base.OnElementChanged(e);
if (Control != null && Element is ExtendedTimePicker element)
{
// center text
Control.Gravity = GravityFlags.CenterHorizontal;
// use placeholder until NullableTime set
if (!element.NullableTime.HasValue)
{
Control.Text = element.PlaceHolder;
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == TimePicker.TimeProperty.PropertyName ||
e.PropertyName == TimePicker.FormatProperty.PropertyName)
{
if (Control != null && Element is ExtendedTimePicker element)
{
if (Element.Format == element.PlaceHolder)
{
Control.Text = element.PlaceHolder;
return;
}
}
}
base.OnElementPropertyChanged(sender, e);
}
}
}

View File

@@ -0,0 +1,95 @@
using System.ComponentModel;
using Android.Content;
using Android.Webkit;
using Bit.App.Controls;
using Java.Interop;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
using AWebkit = Android.Webkit;
namespace Bit.App.Droid.Renderers
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
{
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
private readonly Context _context;
public HybridWebViewRenderer(Context context)
: base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new AWebkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(Element.Uri);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == HybridWebView.UriProperty.PropertyName)
{
Control.LoadUrl(Element.Uri);
}
}
public class JSBridge : Java.Lang.Object
{
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
_hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
if (_hybridWebViewRenderer != null &&
_hybridWebViewRenderer.TryGetTarget(out HybridWebViewRenderer hybridRenderer))
{
hybridRenderer.Element.InvokeAction(data);
}
}
}
public class JSWebViewClient : WebViewClient
{
private readonly string _javascript;
public JSWebViewClient(string javascript)
{
_javascript = javascript;
}
public override void OnPageFinished(AWebkit.WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript(_javascript, null);
}
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using Android.Content;
using Bit.App.Controls;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Microsoft.Maui.Controls.Platform;
namespace Bit.App.Droid.Renderers
{
public class SelectableLabelRenderer : LabelRenderer
{
public SelectableLabelRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.SetTextIsSelectable(true);
}
}
}
}

Binary file not shown.

View File

@@ -59,11 +59,12 @@
<Setter Property="FontAttributes"
Value="Bold" />
</Style>
<!--[MAUI-Migration][Color-default]-->
<Style TargetType="Label"
Class="text-html"
ApplyToDerivedTypes="True">
<Setter Property="TextColor"
Value="Default" />
Value="#ffffff" />
<Setter Property="TextType"
Value="Html" />
</Style>

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.Light">
@@ -35,7 +35,8 @@
<Color x:Key="ListHeaderTextColor">#175DDC</Color>
<Color x:Key="ListHeaderBackgroundColor">#efeff4</Color>
<Color x:Key="SliderThumbColor">Default</Color>
<!--[MAUI-Migration][Color-default]-->
<Color x:Key="SliderThumbColor">#ffffff</Color>
<Color x:Key="SliderThumbBorderColor">#b5b5b5</Color>
<Color x:Key="SliderTrackMinColor">#175DDC</Color>
<Color x:Key="SliderTrackMaxColor">#dddddd</Color>