Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>

<pages:BasePage
x:Class="CommunityToolkit.Maui.Sample.Pages.Behaviors.StatusBarBehaviorPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
Expand Down Expand Up @@ -118,9 +119,9 @@
Value="{Binding BlueSliderValue}" />
<HorizontalStackLayout>
<Label Text="Alpha: " />
<Label Text="{Binding AlphaSliderValue}"/>
<Label Text="{Binding AlphaSliderValue}" />
</HorizontalStackLayout>

<Stepper
HorizontalOptions="Start"
Margin="20,0"
Expand All @@ -130,36 +131,47 @@
Value="{Binding AlphaSliderValue}" />

<Label Text="Select StatusBar and NavigationBar style" />

<HorizontalStackLayout Spacing="15">



<RadioButton
Content="Default"
IsChecked="{Binding IsDefaultChecked}"
VerticalOptions="Center" />
<HorizontalStackLayout Spacing="15">
<RadioButton IsChecked="{Binding IsDefaultChecked}"
VerticalOptions="Center">
<RadioButton.Content>
<Label
Margin="10,0,0,0"
Text="Default"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
</RadioButton.Content>
</RadioButton>

<RadioButton VerticalOptions="Center" IsChecked="{Binding IsLightContentChecked}">
<RadioButton IsChecked="{Binding IsLightContentChecked}"
VerticalOptions="Center">
<RadioButton.Content>
<Label Text="Light Content" Margin="10,0,0,0" VerticalTextAlignment="Center"
VerticalOptions="Center" />
<Label
Margin="10,0,0,0"
Text="Light&#10;Content"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
</RadioButton.Content>
</RadioButton>

<RadioButton IsChecked="{Binding IsDarkContentChecked}">
<RadioButton IsChecked="{Binding IsDarkContentChecked}"
VerticalOptions="Center">
<RadioButton.Content>
<Label Text="Dark Content" Margin="10,0,0,0" VerticalTextAlignment="Center"
VerticalOptions="Center" />
<Label
Margin="10,0,0,0"
Text="Dark&#10;Content"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
</RadioButton.Content>
</RadioButton>

</HorizontalStackLayout>

<Button
x:Name="ModalPageButton"
HorizontalOptions="Center"
VerticalOptions="Center"/>
VerticalOptions="Center" />
</VerticalStackLayout>
</ScrollView>
</pages:BasePage>
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@
ThumbColor="Blue"
Value="{Binding BlueSliderValue}" />

<VerticalStackLayout Spacing="15">
<Label Text="Select NavigationBar style" />
<Label Text="Select NavigationBar style" />
<HorizontalStackLayout Spacing="15">

<RadioButton IsChecked="{Binding IsDefaultChecked}" VerticalOptions="Center">
<RadioButton.Content>
Expand All @@ -125,7 +125,7 @@
<RadioButton.Content>
<Label
Margin="10,0,0,0"
Text="Light Content"
Text="Light&#10;Content"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
</RadioButton.Content>
Expand All @@ -135,12 +135,12 @@
<RadioButton.Content>
<Label
Margin="10,0,0,0"
Text="Dark Content"
Text="Dark&#10;Content"
VerticalOptions="Center"
VerticalTextAlignment="Center" />
</RadioButton.Content>
</RadioButton>
</VerticalStackLayout>
</HorizontalStackLayout>
</VerticalStackLayout>
</ScrollView>
</pages:BasePage>
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,6 @@ namespace CommunityToolkit.Maui.Sample.ViewModels.Behaviors;

public partial class StatusBarBehaviorViewModel : BaseViewModel
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double RedSliderValue { get; set; }

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double GreenSliderValue { get; set; }

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double BlueSliderValue { get; set; }

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double AlphaSliderValue { get; set; } = 1;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarStyle))]
public partial bool IsLightContentChecked { get; set; } = true;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarStyle))]
public partial bool IsDarkContentChecked { get; set; } = true;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarStyle))]
public partial bool IsDefaultChecked { get; set; } = true;

public Color StatusBarColor => Color.FromRgba(RedSliderValue, GreenSliderValue, BlueSliderValue, AlphaSliderValue);

public StatusBarStyle StatusBarStyle
Expand All @@ -55,4 +27,32 @@ public StatusBarStyle StatusBarStyle
throw new NotSupportedException($"{nameof(StatusBarStyle)} {StatusBarStyle} is not supported.");
Comment thread
TheCodeTraveler marked this conversation as resolved.
Outdated
}
}

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double RedSliderValue { get; set; } = 0.5;
Comment thread
TheCodeTraveler marked this conversation as resolved.
Outdated

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double GreenSliderValue { get; set; } = 0.5;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double BlueSliderValue { get; set; } = 0.5;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarColor))]
public partial double AlphaSliderValue { get; set; } = 1;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarStyle))]
public partial bool IsLightContentChecked { get; set; }

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarStyle))]
public partial bool IsDarkContentChecked { get; set; }

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(StatusBarStyle))]
public partial bool IsDefaultChecked { get; set; } = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,57 +35,58 @@ static void PlatformSetColor(Color color)
return;
}

if (Activity.Window is not null)
if (GetCurrentWindow() is not Window { DecorView.RootView: not null } window)
{
var platformColor = color.ToPlatform();
return;
}
Comment thread
TheCodeTraveler marked this conversation as resolved.

if (OperatingSystem.IsAndroidVersionAtLeast(35))
{
const string statusBarOverlayTag = "StatusBarOverlay";
var platformColor = color.ToPlatform();

var window = Activity.GetCurrentWindow();
if (OperatingSystem.IsAndroidVersionAtLeast(35))
{
const string statusBarOverlayTag = "StatusBarOverlay";

var decorGroup = (ViewGroup)window.DecorView;
var statusBarOverlay = decorGroup.FindViewWithTag(statusBarOverlayTag);
var decorGroup = (ViewGroup)window.DecorView.RootView;
Comment thread
TheCodeTraveler marked this conversation as resolved.
var statusBarOverlay = decorGroup.FindViewWithTag(statusBarOverlayTag);
Comment thread
TheCodeTraveler marked this conversation as resolved.

if (statusBarOverlay is null)
{
var statusBarHeight = Activity.Resources?.GetIdentifier("status_bar_height", "dimen", "android") ?? 0;
var statusBarPixelSize = statusBarHeight > 0 ? Activity.Resources?.GetDimensionPixelSize(statusBarHeight) ?? 0 : 0;
if (statusBarOverlay is null)
{
var statusBarHeight = Activity.Resources?.GetIdentifier("status_bar_height", "dimen", "android") ?? 0;
var statusBarPixelSize = statusBarHeight > 0 ? Activity.Resources?.GetDimensionPixelSize(statusBarHeight) ?? 0 : 0;

statusBarOverlay = new(Activity)
statusBarOverlay = new(Activity)
{
LayoutParameters = new FrameLayout.LayoutParams(Android.Views.ViewGroup.LayoutParams.MatchParent, statusBarPixelSize + 3)
{
LayoutParameters = new FrameLayout.LayoutParams(Android.Views.ViewGroup.LayoutParams.MatchParent, statusBarPixelSize + 3)
{
Gravity = GravityFlags.Top
}
};

decorGroup.AddView(statusBarOverlay);
statusBarOverlay.SetZ(0);
}
Gravity = GravityFlags.Top
}
};

Comment thread
TheCodeTraveler marked this conversation as resolved.
statusBarOverlay.SetBackgroundColor(platformColor);
}
else
{
Activity.Window.SetStatusBarColor(platformColor);
statusBarOverlay.Tag = statusBarOverlayTag;
decorGroup.AddView(statusBarOverlay);
statusBarOverlay.SetZ(0);
}

bool isColorTransparent = platformColor == PlatformColor.Transparent;
if (isColorTransparent)
{
Activity.Window.ClearFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
Activity.Window.SetFlags(WindowManagerFlags.LayoutNoLimits, WindowManagerFlags.LayoutNoLimits);
}
else
{
Activity.Window.ClearFlags(WindowManagerFlags.LayoutNoLimits);
Activity.Window.SetFlags(WindowManagerFlags.DrawsSystemBarBackgrounds, WindowManagerFlags.DrawsSystemBarBackgrounds);
}
statusBarOverlay.SetBackgroundColor(platformColor);
}
else
{
window.SetStatusBarColor(platformColor);
}

WindowCompat.SetDecorFitsSystemWindows(Activity.Window, !isColorTransparent);
bool isColorTransparent = platformColor == PlatformColor.Transparent;
if (isColorTransparent)
{
window.ClearFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't know any other way to deal with issue of status bar transparency but settings and clearing flags can and will interfere with developers attempts to set or use full screen and in a test app I had to remove all the settings and clearing of flags to have my app do what I wanted. I removed it throughout the toolkit where I found it in statusbar.

I am not sure what we should do here. This is more a comment about the current state of the behavior of hiding/showing status bar is a complicated mess atm and depending on API level settings these options will have completely different behavior on each API.

I do not advocate ripping out the flags I am simply stating all of my use cases do require it. It would have significant impact on all API below 36 so I am not suggesting we do that. Just maybe add checks and block the changes for android 36?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It might be a good idea to add full-screen mode to CommunityToolkit.Maui so it doesn't interfere with the color change. We could also use an IsVisible boolean on the status bar and navigation bar.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@IgrisModz sure! What specifically do we need to add?

Do you have any links to documentation you could share?

window.SetFlags(WindowManagerFlags.LayoutNoLimits, WindowManagerFlags.LayoutNoLimits);
}
else
{
window.ClearFlags(WindowManagerFlags.LayoutNoLimits);
window.SetFlags(WindowManagerFlags.DrawsSystemBarBackgrounds, WindowManagerFlags.DrawsSystemBarBackgrounds);
}

WindowCompat.SetDecorFitsSystemWindows(window, !isColorTransparent);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This method is deprecated on Android API 36. It is highly recommended that we add a check and only use it on an OS using a lower API.

}

static void PlatformSetStyle(StatusBarStyle style)
Expand All @@ -98,25 +99,32 @@ static void PlatformSetStyle(StatusBarStyle style)
switch (style)
{
case StatusBarStyle.DarkContent:
SetStatusBarAppearance(Activity, true);
SetStatusBarAppearance(true);
break;

case StatusBarStyle.Default:
case StatusBarStyle.LightContent:
SetStatusBarAppearance(Activity, false);
SetStatusBarAppearance(false);
break;

default:
throw new NotSupportedException($"{nameof(StatusBarStyle)} {style} is not yet supported on Android");
}
}

static void SetStatusBarAppearance(Activity activity, bool isLightStatusBars)
static void SetStatusBarAppearance(bool isLightStatusBars)
{
var window = activity.GetCurrentWindow();
if (WindowCompat.GetInsetsController(window, window.DecorView) is WindowInsetsControllerCompat windowController)
if(GetCurrentWindow() is Window window
Comment thread
TheCodeTraveler marked this conversation as resolved.
Outdated
&& WindowCompat.GetInsetsController(window, window.DecorView) is WindowInsetsControllerCompat windowController)
{
windowController.AppearanceLightStatusBars = isLightStatusBars;
}
}

// Check if a modal DialogFragment is active and use its window
static Window? GetCurrentWindow()
{
var dialogFragment = Activity.GetFragmentManager()?.Fragments.OfType<AndroidX.Fragment.App.DialogFragment>().LastOrDefault();
return dialogFragment?.Dialog?.Window ?? Activity.Window; // Fall back to the Activity window for non-modal pages
Comment thread
TheCodeTraveler marked this conversation as resolved.
Outdated
Comment thread
TheCodeTraveler marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,8 @@ namespace CommunityToolkit.Maui.Extensions;
static class PropertyChangedEventArgsExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Is(this string propertyName, BindableProperty property) =>
propertyName == property.PropertyName;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOneOf(this string propertyName, BindableProperty p0, BindableProperty p1)
{
return propertyName == p0.PropertyName
|| propertyName == p1.PropertyName;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOneOf(this string propertyName, BindableProperty p0, BindableProperty p1, BindableProperty p2)
{
return propertyName == p0.PropertyName
|| propertyName == p1.PropertyName
|| propertyName == p2.PropertyName;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOneOf(this string propertyName, BindableProperty p0, BindableProperty p1, BindableProperty p2, BindableProperty p3)
{
return propertyName == p0.PropertyName
|| propertyName == p1.PropertyName
|| propertyName == p2.PropertyName
|| propertyName == p3.PropertyName;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOneOf(this string propertyName, BindableProperty p0, BindableProperty p1, BindableProperty p2, BindableProperty p3, BindableProperty p4)
public static bool IsOneOf(this string propertyName, params IReadOnlyList<BindableProperty> bindableProperties)
Comment thread
TheCodeTraveler marked this conversation as resolved.
{
return propertyName == p0.PropertyName
|| propertyName == p1.PropertyName
|| propertyName == p2.PropertyName
|| propertyName == p3.PropertyName
|| propertyName == p4.PropertyName;
return bindableProperties.Any(bindableProperty => bindableProperty.PropertyName == propertyName);
Comment thread
TheCodeTraveler marked this conversation as resolved.
}
Comment thread
TheCodeTraveler marked this conversation as resolved.
}
Loading