Version 3.0.381

This commit is contained in:
Anja 2024-04-09 12:53:23 +02:00
parent f963c0a219
commit 3a363acf3a
1525 changed files with 60589 additions and 125098 deletions

View file

@ -0,0 +1,19 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace ShareeBike.View
{
/// <summary> Inverts a bool.</summary>
public class BoolInverterConverter : IValueConverter
{
/// <summary> Inverts a bool.</summary>
/// <param name="value">Bool to invert.</param>
/// <returns>Inverted bool.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool flag && !flag;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool flag && !flag;
}
}

View file

@ -0,0 +1,126 @@
using System.Threading.Tasks;
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
namespace ShareeBike.View
{
public interface IViewService
{
/// <summary> Displays alert message. </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Text of button.</param>
Task DisplayAlert(
string title,
string message,
string cancel);
/// <summary> Displays alert message. </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="cancel">Text of button.</param>
Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel);
/// <summary> Displays alert message. </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
Task<bool> DisplayAlert(string title, string message, string accept, string cancel);
/// <summary> Displays alert message. </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="details">Detailed error description.</param>
/// <param name="accept">Text of accept button.</param>
/// <param name="cancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel);
/// <summary> Displays an action sheet. </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Text of button.</param>
/// <param name="destruction"></param>
/// <param name="buttons">Buttons holding options to select.</param>
/// <returns>T</returns>
Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons);
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
#if USCSHARP9
public Task ShowPage(string route);
#else
Task ShowPage(string route);
#endif
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
Task PushAsync(ViewTypes typeOfPage);
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
Task PushModalAsync(ViewTypes typeOfPage);
/// <summary> Pushes a page onto the modal stack. </summary>
Task PopModalAsync();
/// <summary> Displays user feedback popup.</summary>
/// <param name="battery">Object holding info about battery. For some batteries charging level might need to be updated by user.</param>
/// <returns>User feedback.</returns>
Task<IUserFeedback> DisplayUserFeedbackPopup(
IBatteryMutable battery = null);
#if USCSHARP9
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
public interface IUserFeedback
{
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
bool IsBikeBroken { get; set; }
/// <summary>
/// Holds either
/// - general feedback
/// - error description of broken bike
/// or both.
/// </summary>
string Message { get; set; }
}
#endif
}
#if !USCSHARP9
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
public interface IUserFeedback
{
/// <summary>
/// Holds the current charging level of the battery in bars, null if unknown.
/// </summary>
int? CurrentChargeBars { get; set; }
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
bool IsBikeBroken { get; set; }
/// <summary>
/// Holds either
/// - general feedback
/// - error description of broken bike
/// or both.
/// </summary>
string Message { get; set; }
}
#endif
}

View file

@ -0,0 +1,35 @@
using System.Windows.Input;
using Xamarin.Forms;
namespace ShareeBike.View
{
public static class ListViewAttachedBehavior
{
public static readonly BindableProperty CommandProperty =
BindableProperty.CreateAttached(
"Command",
typeof(ICommand),
typeof(ListViewAttachedBehavior),
null,
propertyChanged: OnCommandChanged);
static void OnCommandChanged(BindableObject view, object oldValue, object newValue)
{
var entry = view as ListView;
if (entry == null)
return;
entry.ItemTapped += (sender, e) =>
{
var command = (newValue as ICommand);
if (command == null)
return;
if (command.CanExecute(e.Item))
{
command.Execute(e.Item);
}
};
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Globalization;
using ShareeBike.Model.User.Account;
using Xamarin.Forms;
namespace ShareeBike.View.Settings
{
/// <summary>
/// Translates user permissions into visibility state.
/// Used for container which holds a bunch of GUI elemets which migth all/ partly/ none be visible
/// If all childs are invisible frame must be invisible as well. As soon as one child is visible frame must be visible as well.
/// </summary>
public class BackendPermissionsToVisibleConverter : IValueConverter
{
/// <summary> Converts permission value into visible state.</summary>
/// <param name="value">Permission value from view model used to derive whether object is visible or not.</param>
/// <returns>Boolean value indicating whether object is visible or not.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumValue = (Permissions)value;
return enumValue.HasFlag(Permissions.PickCopriServer) | enumValue.HasFlag(Permissions.ManagePolling) | enumValue.HasFlag(Permissions.ManageCopriCacheExpiration);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Permissions.None;
}
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.Globalization;
using ShareeBike.Model.User.Account;
using Xamarin.Forms;
namespace ShareeBike.View.Settings
{
/// <summary> Translates user permissions into visibility state. </summary>
public class PermissionToVisibleConverter : BindableObject, IValueConverter
{
static readonly BindableProperty VisibleFlagProperty =
BindableProperty.Create(nameof(VisibleFlag), typeof(Permissions), typeof(BindableObject));
/// <summary> Property set from XAML determinig for which permission value object is visible.</summary>
public Permissions VisibleFlag
{
get => (Permissions)GetValue(VisibleFlagProperty);
set => SetValue(VisibleFlagProperty, value);
}
/// <summary> Converts permission value into visible state.</summary>
/// <param name="value">Permission value from view model used to derive whether object is visible or not.</param>
/// <returns>Boolean value indicating whether object is visible or not.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((Permissions)value).HasFlag(VisibleFlag);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Permissions.None;
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace ShareeBike.View
{
/// <summary> Converts a string into visible state. If string is null or empty element becomes invisible.</summary>
public class StringNotNullOrEmptyToVisibleConverter : IValueConverter
{
/// <summary> Converts a string into visible state.</summary>
/// <param name="value">Text value from view model used to derive whether object is visible or not.</param>
/// <returns>Boolean value indicating whether object is visible or not.</returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null && value is string text && !string.IsNullOrEmpty(text);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return "";
}
}
}

View file

@ -0,0 +1,7 @@
namespace ShareeBike.View.Themes
{
public interface ITheme
{
string OperatorInfo { get; }
}
}

View file

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
x:Class="ShareeBike.Themes.LastenradBayern">
<!--Main color-->
<Color x:Key="primary-back-title-color">#009BDB</Color>
<Color x:Key="bright-primary-back-title-color">#C5E6E6</Color>
<!--Background color-->
<Color x:Key="background-color">#BEBEBE</Color>
<!--Important text color-->
<Color x:Key="important-text-color">#ff0000</Color>
<!--Attention color-->
<Color x:Key="attention-color">#FF9214</Color>
<!--RentalProcess colors-->
<Color x:Key="process-step-upcoming">#999999</Color>
<Color x:Key="process-step-active">#0D0D0D</Color>
<Color x:Key="process-step-succeeded">#00813e</Color>
<Color x:Key="process-step-failed">#ff0000</Color>
<!--Primary Button-->
<Style TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="0" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="BackgroundColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="TextColor" Value="White"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="BorderColor" Value="DimGray"/>
<Setter Property="BackgroundColor" Value="DimGray"/>
<Setter Property="TextColor" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Secondary Button-->
<Style x:Key="SecondaryButton" TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="0" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="BackgroundColor" Value="White"/>
<Setter Property="TextColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="BorderColor" Value="DimGray"/>
<Setter Property="BackgroundColor" Value="LightGray"/>
<Setter Property="TextColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Button without outline-->
<Style x:Key="NoOutlineButtonWhite" TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="0" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="White"/>
<Setter Property="BackgroundColor" Value="White"/>
<Setter Property="TextColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="TextColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="NoOutlineButtonBackgroundColor" TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="0" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="{DynamicResource Key=background-color}"/>
<Setter Property="BackgroundColor" Value="{DynamicResource Key=background-color}"/>
<Setter Property="TextColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="TextColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Frame-->
<Style TargetType="Frame">
<Setter Property="CornerRadius" Value="0" />
<Setter Property="HasShadow" Value="False" />
</Style>
<Style x:Key="RunningProcessFrame" TargetType="Frame">
<Setter Property="CornerRadius" Value="0" />
<Setter Property="HasShadow" Value="False" />
</Style>
<!--Switch-->
<Style TargetType="Switch">
<Style.Triggers>
<Trigger TargetType="Switch"
Property="IsToggled"
Value="True">
<Setter Property="ThumbColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="OnColor" Value="{DynamicResource Key=bright-primary-back-title-color}"/>
</Trigger>
<Trigger TargetType="Switch"
Property="IsToggled"
Value="False">
<Setter Property="ThumbColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Slider-->
<Style TargetType="Slider">
<Setter Property="ThumbColor" Value="{DynamicResource Key=primary-back-title-color}"/>
</Style>
<!--Label-->
<Style TargetType="Label">
<Setter Property="FontSize" Value="Default"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Style.Triggers>
<Trigger TargetType="Label"
Property="FontAttributes"
Value="Bold">
<Setter Property="FontFamily" Value="RobotoBold"/>
</Trigger>
<Trigger TargetType="Label"
Property="FontAttributes"
Value="Italic">
<Setter Property="FontFamily" Value="RobotoItalic"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Entry-->
<Style TargetType="Entry">
<Setter Property="FontSize" Value="Default"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
</Style>
<!--Flyout Item-->
<Style TargetType="FlyoutItem">
<Setter Property="Shell.BackgroundColor" Value="{DynamicResource Key=primary-back-title-color}" />
</Style>
<!--Navbar-->
<Style x:Key="Label-Navbar" TargetType="Label">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="TextColor" Value="White"/>
<Setter Property="VerticalOptions" Value="Start"/>
<Setter Property="HorizontalOptions" Value="Start"/>
<Setter Property="Padding" Value="0,14,0,12"/>
<Setter Property="MaxLines" Value="1"/>
</Style>
<Style x:Key="Image-Navbar" TargetType="Image">
<Setter Property="Source" Value="navbar_theme.png"/>
<Setter Property="Aspect" Value="AspectFill"/>
<Setter Property="Grid.ColumnSpan" Value="2"/>
</Style>
<!--TabbedPage-->
<Style x:Key="TabbedPageStyle" TargetType="TabbedPage">
<Setter Property="BarBackgroundColor" Value="White"/>
<Setter Property="UnselectedTabColor" Value="DimGray"/>
<Setter Property="SelectedTabColor" Value="{x:DynamicResource primary-back-title-color}"/>
<Setter Property="android:TabbedPage.ToolbarPlacement" Value="Default"/>
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,19 @@
using ShareeBike.View.Themes;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ShareeBike.Themes
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LastenradBayern : ResourceDictionary, ITheme
{
public const string OPERATORINFO = "Lastenrad Bayern";
public LastenradBayern()
{
InitializeComponent();
}
public string OperatorInfo => OPERATORINFO;
}
}

View file

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
x:Class="ShareeBike.Themes.ShareeBike">
<!--Main color-->
<Color x:Key="primary-back-title-color">#009899</Color>
<Color x:Key="bright-primary-back-title-color">#B5E6E6</Color>
<!--Background color-->
<Color x:Key="background-color">#BEBEBE</Color>
<!--Important text color-->
<Color x:Key="important-text-color">#BF2828</Color>
<!--Attention color-->
<Color x:Key="attention-color">#FC870D</Color>
<!--RentalProcess colors-->
<Color x:Key="process-step-upcoming">#999999</Color>
<Color x:Key="process-step-active">#0D0D0D</Color>
<Color x:Key="process-step-succeeded">#5DBF3B</Color>
<Color x:Key="process-step-failed">#BF2828</Color>
<!--Primary Button-->
<Style TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="10" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="BackgroundColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="TextColor" Value="White"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="BorderColor" Value="DimGray"/>
<Setter Property="BackgroundColor" Value="DimGray"/>
<Setter Property="TextColor" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Secondary Button-->
<Style x:Key="SecondaryButton" TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="10" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="BackgroundColor" Value="White"/>
<Setter Property="TextColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="BorderColor" Value="DimGray"/>
<Setter Property="BackgroundColor" Value="LightGray"/>
<Setter Property="TextColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Button without outline-->
<Style x:Key="NoOutlineButtonWhite" TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="10" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="White"/>
<Setter Property="BackgroundColor" Value="White"/>
<Setter Property="TextColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="TextColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="NoOutlineButtonBackgroundColor" TargetType="Button">
<Setter Property="WidthRequest" Value="400" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="BorderWidth" Value="1"/>
<Setter Property="CornerRadius" Value="10" />
<Setter Property="FontSize" Value="Medium"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="BorderColor" Value="{DynamicResource Key=background-color}"/>
<Setter Property="BackgroundColor" Value="{DynamicResource Key=background-color}"/>
<Setter Property="TextColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Style.Triggers>
<Trigger TargetType="Button"
Property="IsEnabled"
Value="False">
<Setter Property="TextColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Frame-->
<Style TargetType="Frame">
<Setter Property="CornerRadius" Value="0" />
<Setter Property="HasShadow" Value="False" />
</Style>
<Style x:Key="RunningProcessFrame" TargetType="Frame">
<Setter Property="CornerRadius" Value="10" />
<Setter Property="HasShadow" Value="False" />
</Style>
<!--Switch-->
<Style TargetType="Switch">
<Style.Triggers>
<Trigger TargetType="Switch"
Property="IsToggled"
Value="True">
<Setter Property="ThumbColor" Value="{DynamicResource Key=primary-back-title-color}"/>
<Setter Property="OnColor" Value="{DynamicResource Key=bright-primary-back-title-color}"/>
</Trigger>
<Trigger TargetType="Switch"
Property="IsToggled"
Value="False">
<Setter Property="ThumbColor" Value="DimGray"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Slider-->
<Style TargetType="Slider">
<Setter Property="ThumbColor" Value="{DynamicResource Key=primary-back-title-color}"/>
</Style>
<!--Label-->
<Style TargetType="Label">
<Setter Property="FontSize" Value="Default"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Style.Triggers>
<Trigger TargetType="Label"
Property="FontAttributes"
Value="Bold">
<Setter Property="FontFamily" Value="RobotoBold"/>
</Trigger>
<Trigger TargetType="Label"
Property="FontAttributes"
Value="Italic">
<Setter Property="FontFamily" Value="RobotoItalic"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Entry-->
<Style TargetType="Entry">
<Setter Property="FontSize" Value="Default"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
</Style>
<!--Flyout Item-->
<Style TargetType="FlyoutItem">
<Setter Property="Shell.BackgroundColor" Value="{DynamicResource Key=primary-back-title-color}" />
</Style>
<!--Navbar-->
<Style x:Key="Label-Navbar" TargetType="Label">
<Setter Property="FontSize" Value="20"/>
<Setter Property="FontFamily" Value="RobotoRegular"/>
<Setter Property="TextColor" Value="White"/>
<Setter Property="VerticalOptions" Value="Start"/>
<Setter Property="HorizontalOptions" Value="Start"/>
<Setter Property="Padding" Value="0,14,0,12"/>
<Setter Property="MaxLines" Value="1"/>
</Style>
<Style x:Key="Image-Navbar" TargetType="Image">
<Setter Property="Source" Value="navbar_theme.png"/>
<Setter Property="Aspect" Value="AspectFill"/>
<Setter Property="Grid.ColumnSpan" Value="2"/>
</Style>
<!--TabbedPage-->
<Style x:Key="TabbedPageStyle" TargetType="TabbedPage">
<Setter Property="BarBackgroundColor" Value="White"/>
<Setter Property="UnselectedTabColor" Value="DimGray"/>
<Setter Property="SelectedTabColor" Value="{x:DynamicResource primary-back-title-color}"/>
<Setter Property="android:TabbedPage.ToolbarPlacement" Value="Default"/>
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,19 @@
using ShareeBike.View.Themes;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace ShareeBike.Themes
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ShareeBike : ResourceDictionary, ITheme
{
public const string OPERATORINFO = "sharee.bike";
public ShareeBike()
{
InitializeComponent();
}
public string OperatorInfo => OPERATORINFO;
}
}

View file

@ -0,0 +1,175 @@
using System.Linq;
using Serilog;
using ShareeBike.Model.Device;
using Xamarin.Forms;
namespace ShareeBike.View
{
public static class WebViewHelper
{
/// <summary> Rountes request to browser if internal URI was detected or displays link in app otherwise. </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="eventArgs">Event arguments</param>
public static void SelectDisplayTarget(object sender, WebNavigatingEventArgs eventArgs)
{
if (!eventArgs.Url.ToUpper().StartsWith("HTTP"))
{
// An internal link was detected.
// Stay inside WebView
eventArgs.Cancel = false;
return;
}
// Do not navigate outside the document on web view.
eventArgs.Cancel = true;
// Open external link in browser.
DependencyService.Get<IExternalBrowserService>().OpenUrl(eventArgs.Url);
}
/// <summary> Routes request to broswer to download pdf- documents or displays link in app otherwise. </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="eventArgs">Event arguments</param>
public static void SetBusyAndDisplayOrDownload(object sender, WebNavigatingEventArgs eventArgs)
{
if (!eventArgs.Url.ToUpper().EndsWith(".PDF"))
{
// Stay inside web view except for downloading pdf- files.
SetBusy(sender);
eventArgs.Cancel = false;
return;
}
// Do not navigate outside the document on web view.
eventArgs.Cancel = true;
// Open external link in browser.
DependencyService.Get<IExternalBrowserService>().OpenUrl(eventArgs.Url);
}
/// <summary>
/// Disables WebView and shows ActivityIndicator.
/// </summary>
/// <remarks>
/// Prerequisites: Both WebView and ActivityInicator must be located on the same Grid object.
/// </remarks>
/// <param name="sender"></param>
private static void SetBusy(object sender)
{
if (!(sender is WebView webView))
{
// Nothing to do if sender is not a web view.
Log.Error($"Unexpected call of {nameof(SetBusy)} detected. Argument must not be of type {sender.GetType()}.");
return;
}
if (!(webView.Parent is Grid grid))
{
// Nothing to do if web view is not located on grid.
Log.Error($"Unexpected call of {nameof(SetBusy)} detected. Parent of argument must not be of type {webView.Parent.GetType()}.");
return;
}
grid.IsEnabled = false;
var indicator = grid.Children.OfType<ActivityIndicator>().FirstOrDefault();
if (indicator == null)
{
// Nothing to do if there is not activity indicator.
Log.Error($"Unexpected call of {nameof(SetBusy)} detected. No activity indicator found.");
return;
}
indicator.IsVisible = true;
indicator.IsRunning = true;
}
/// <summary>
/// Enables WebView and hides ActivityIndicator and sets error text to WebView in case an error occurred.
/// </summary>
/// <remarks>
/// Prerequisites: Both WebView and ActivityInicator must be located on the same Grid object.
/// </remarks>
/// <param name="htmlErrorMessage">HTML showing an error message.</param>
public static void SetIdleAndHandleError(
object sender,
WebNavigatedEventArgs eventArgs,
string htmlErrorMessage)
{
if (!(sender is WebView webView))
{
// Nothing to do if sender is not a web view.
Log.Error($"Unexpected call of {nameof(SetIdleAndHandleError)} detected. Argument must not be of type {sender.GetType()}.");
return;
}
if (!(webView.Parent is Grid grid))
{
// Nothing to do if web view is not located on grid.
Log.Error($"Unexpected call of {nameof(SetIdleAndHandleError)} detected. Parent of argument must not be of type {webView.Parent.GetType()}.");
return;
}
var indicator = grid.Children.OfType<ActivityIndicator>().FirstOrDefault();
if (indicator == null)
{
// Nothing to do if there is not activity indicator.
Log.Error($"Unexpected call of {nameof(SetIdleAndHandleError)} detected. No activity indicator found.");
}
if (eventArgs.Result == WebNavigationResult.Success)
{
// Uri could be loaded successfully.
grid.IsEnabled = true;
if (indicator != null)
{
indicator.IsVisible = false;
indicator.IsRunning = false;
}
return;
}
// Navigation failed.
Log.Error("Navigation did not succeed.{@Event}{@Sender}", eventArgs, sender);
webView.Source = new HtmlWebViewSource
{
Html = !string.IsNullOrEmpty(htmlErrorMessage) ? htmlErrorMessage : $"Navigation to {eventArgs.Url} failed."
};
grid.IsEnabled = true;
if (indicator != null)
{
indicator.IsVisible = false;
indicator.IsRunning = false;
}
}
/// <summary>
/// Sets error text to WebView in case an error occurred.
/// </summary>
/// <param name="htmlErrorMessage">HTML showing an error message.</param>
public static void HandleError(
object sender,
WebNavigatedEventArgs eventArgs,
string htmlErrorMessage)
{
if (eventArgs.Result == WebNavigationResult.Success)
{
// Uri could be loaded successfully.
return;
}
if (!(sender is WebView webView))
{
// Nothing to do if sender is not a web view.
Log.Error($"Unexpected call of {nameof(HandleError)} detected. Argument must not be of type {sender.GetType()}.");
return;
}
// Navigation failed.
Log.Error("Navigation did not succeed.{@Event}{@Sender}", eventArgs, sender);
webView.Source = new HtmlWebViewSource
{
Html = !string.IsNullOrEmpty(htmlErrorMessage) ? htmlErrorMessage : $"Navigation to {eventArgs.Url} failed."
};
}
}
}