Version 3.0.255

This commit is contained in:
Oliver Hauff 2021-11-07 19:42:59 +01:00
parent db9c288584
commit 5a26bf273b
1495 changed files with 159465 additions and 5060 deletions

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="TINK.View.Account.AccountPage">
<ContentPage.Content>
<Frame>
<StackLayout>
<Frame>
<StackLayout>
<Label Text="{Binding LoggedInInfo}" />
<Label IsVisible="{Binding IsBookingStateInfoVisible}"
Text="{Binding BookingStateInfo}" />
<Button Text="Persönliche Daten Verwalten"
Command="{Binding OnManageAccount}"
IsEnabled="{Binding IsLogoutPossible}"/>
<Button Text="Abmelden"
Command="{Binding OnLogoutRequest}"
IsEnabled="{Binding IsLogoutPossible}"/>
</StackLayout>
</Frame>
</StackLayout>
</Frame>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,155 @@
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using System;
using TINK.Model.Device;
using TINK.ViewModel.Account;
namespace TINK.View.Account
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class AccountPage : ContentPage, IViewService, IDetailPage
#else
public partial class AccountPage : ContentPage, IViewService
#endif
{
/// <summary> Refernce to view model. </summary>
AccountPageViewModel m_oViewModel = null;
/// <summary> Constructs a account page. </summary>
public AccountPage()
{
InitializeComponent();
var l_oModel = App.ModelRoot;
m_oViewModel = new AccountPageViewModel(
l_oModel,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url),
this);
BindingContext = m_oViewModel;
}
/// <summary> Displays alert message. </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAlert(string title, string message, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, 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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", 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>
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, 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="p_oButtons">Buttons holding options to select.</param>
/// <returns>Text selected</returns>
public new async Task<string> DisplayActionSheet(String title, String cancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(title, cancel, destruction, p_oButtons);
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string title = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushModalAsync(ViewTypes typeOfPage)
=> Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync()
=> throw new NotSupportedException();
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigation;
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigation = value; }
}
#endif
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
protected async override void OnAppearing()
=> await m_oViewModel.OnAppearing();
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
protected async override void OnDisappearing()
{
if (m_oViewModel == null)
{
// View model might be null.
return;
}
await m_oViewModel.OnDisappearing();
}
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes p_oTypeOfPage)
=> await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:conv="clr-namespace:TINK.View"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
mc:Ignorable="d"
x:Class="TINK.View.Bike.BCBike">
<ContentView>
<ContentView.Resources>
<conv:StringNotNullOrEmptyToVisibleConverter x:Key="Label_Converter"/>
</ContentView.Resources>
<StackLayout
Padding="10">
<Label
FontAttributes="Bold"
Text="{Binding Name}"/>
<Label
Text="{Binding StateText}"
TextColor="{Binding StateColor}"/>
<Label
Text="{Binding ErrorText}"
IsVisible="{Binding ErrorText, Converter={StaticResource Label_Converter}}"
TextColor="Red"/>
<Button
Text="{Binding ButtonText}"
IsVisible="{Binding IsButtonVisible}"
IsEnabled="{Binding IsIdle}"
Command="{Binding OnButtonClicked}"/>
<Grid
IsVisible="{Binding TariffDescription.Header, Converter={StaticResource Label_Converter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Text=
"{Binding TariffDescription.Header}"
Grid.ColumnSpan="3"
FontAttributes="Bold"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionFreeTimePerSession}"
IsVisible="{Binding TariffDescription.FreeTimePerSession, Converter={StaticResource Label_Converter}}"
Grid.Row="1"/>
<Label
Text="{Binding TariffDescription.FreeTimePerSession}"
IsVisible="{Binding TariffDescription.FreeTimePerSession, Converter={StaticResource Label_Converter}}"
Grid.Row="1"
Grid.Column="1"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionFeeEuroPerHour}"
IsVisible="{Binding TariffDescription.FeeEuroPerHour, Converter={StaticResource Label_Converter}}"
Grid.Row="2"/>
<Label
Text="{Binding TariffDescription.FeeEuroPerHour}"
IsVisible="{Binding TariffDescription.FeeEuroPerHour, Converter={StaticResource Label_Converter}}"
Grid.Row="2"
Grid.Column="1"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionMaxFeeEuroPerDay}"
IsVisible="{Binding TariffDescription.MaxFeeEuroPerDay, Converter={StaticResource Label_Converter}}"
Grid.Row="3"/>
<Label
Text="{Binding TariffDescription.MaxFeeEuroPerDay}"
IsVisible="{Binding TariffDescription.MaxFeeEuroPerDay, Converter={StaticResource Label_Converter}}"
Grid.Row="3"
Grid.Column="1"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionAboEuroPerMonth}"
IsVisible="{Binding TariffDescription.AboEuroPerMonth, Converter={StaticResource Label_Converter}}"
Grid.Row="4"/>
<Label
Text="{Binding TariffDescription.AboEuroPerMonth}"
IsVisible="{Binding TariffDescription.AboEuroPerMonth, Converter={StaticResource Label_Converter}}"
Grid.Row="4"
Grid.Column="1"/>
</Grid>
</StackLayout>
</ContentView>
</ViewCell>

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Bike
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BCBike : ViewCell
{
public BCBike()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,26 @@
using Xamarin.Forms;
namespace TINK.View.Bike
{
/// <summary>
/// Selects different templates for different bike types (BordComputer bikes, iLockIt bikes).
/// </summary>
public class BikeViewCellTemplateSelector : DataTemplateSelector
{
DataTemplate bCBike;
DataTemplate iLockIBike;
public BikeViewCellTemplateSelector()
{
bCBike = new DataTemplate(typeof(BCBike));
iLockIBike = new DataTemplate(typeof(ILockItBike));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return item is TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel
? iLockIBike
: bCBike;
}
}
}

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:conv="clr-namespace:TINK.View"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
mc:Ignorable="d"
x:Class="TINK.View.Bike.ILockItBike">
<ContentView>
<ContentView.Resources>
<conv:StringNotNullOrEmptyToVisibleConverter x:Key="Label_Converter"/>
</ContentView.Resources>
<StackLayout
Padding="10">
<Label
FontAttributes="Bold"
FontSize="Large"
HorizontalTextAlignment="Center"
Text="{Binding Name}"/>
<Label
FontAttributes="Bold"
HorizontalTextAlignment="Center"
IsVisible="{Binding DisplayId, Converter={StaticResource Label_Converter}}"
Text="{Binding DisplayId}"/>
<Label
Text="{Binding StateText}"
TextColor="{Binding StateColor}"/>
<Label
Text="{Binding ErrorText}"
IsVisible="{Binding ErrorText, Converter={StaticResource Label_Converter}}"
TextColor="Red"/>
<Button
Text="{Binding ButtonText}"
IsVisible="{Binding IsButtonVisible}"
IsEnabled="{Binding IsIdle}"
Command="{Binding OnButtonClicked}"/>
<Button
Text="{Binding LockitButtonText}"
IsVisible="{Binding IsLockitButtonVisible}"
IsEnabled="{Binding IsIdle}"
Command="{Binding OnLockitButtonClicked}"/>
<Grid
IsVisible="{Binding TariffDescription.Header, Converter={StaticResource Label_Converter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Text=
"{Binding TariffDescription.Header}"
Grid.ColumnSpan="3"
FontAttributes="Bold"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionFreeTimePerSession}"
IsVisible="{Binding TariffDescription.FreeTimePerSession, Converter={StaticResource Label_Converter}}"
Grid.Row="1"/>
<Label
Text="{Binding TariffDescription.FreeTimePerSession}"
IsVisible="{Binding TariffDescription.FreeTimePerSession, Converter={StaticResource Label_Converter}}"
Grid.Row="1"
Grid.Column="1"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionFeeEuroPerHour}"
IsVisible="{Binding TariffDescription.FeeEuroPerHour, Converter={StaticResource Label_Converter}}"
Grid.Row="2"/>
<Label
Text="{Binding TariffDescription.FeeEuroPerHour}"
IsVisible="{Binding TariffDescription.FeeEuroPerHour, Converter={StaticResource Label_Converter}}"
Grid.Row="2"
Grid.Column="1"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionMaxFeeEuroPerDay}"
IsVisible="{Binding TariffDescription.MaxFeeEuroPerDay, Converter={StaticResource Label_Converter}}"
Grid.Row="3"/>
<Label
Text="{Binding TariffDescription.MaxFeeEuroPerDay}"
IsVisible="{Binding TariffDescription.MaxFeeEuroPerDay, Converter={StaticResource Label_Converter}}"
Grid.Row="3"
Grid.Column="1"/>
<Label
Text="{x:Static resources:AppResources.MessageBikesManagementTariffDescriptionAboEuroPerMonth}"
IsVisible="{Binding TariffDescription.AboEuroPerMonth, Converter={StaticResource Label_Converter}}"
Grid.Row="4"/>
<Label
Text="{Binding TariffDescription.AboEuroPerMonth}"
IsVisible="{Binding TariffDescription.AboEuroPerMonth, Converter={StaticResource Label_Converter}}"
Grid.Row="4"
Grid.Column="1"/>
</Grid>
</StackLayout>
</ContentView>
</ViewCell>

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TINK.Model.Bikes.Bike.BluetoothLock;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Bike
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ILockItBike : ViewCell
{
public ILockItBike()
{
InitializeComponent();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (Device.RuntimePlatform != Device.iOS)
// Update of size is only required for iOS.
return;
var viewModel = BindingContext as TINK.ViewModel.Bikes.Bike.BluetoothLock.BikeViewModel;
if (viewModel == null)
return;
viewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(TINK.ViewModel.Bikes.Bike.BC.RequestHandler.Base<IBikeInfoMutable>.IsButtonVisible)
|| e.PropertyName == nameof(TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler.Base.IsLockitButtonVisible))
{
// Force update of view cell on iOS.
// https://hausource.visualstudio.com/TINK/_workitems/edit/132
ForceUpdateSize();
}
};
}
}
}

View file

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.BikesAtStation.BikesAtStationPage"
xmlns:local_bike="clr-namespace:TINK.View.Bike"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
Title="{Binding Title}">
<ContentPage.Resources>
<ResourceDictionary>
<local_bike:BikeViewCellTemplateSelector x:Key="bikeTemplateSelector"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Frame>
<StackLayout
Orientation="Vertical">
<Label
HorizontalOptions="Center"
FontAttributes="Bold"
Text="{Binding StationDetailText}"/>
<ListView
x:Name="BikesAtStationListView"
SelectionMode="None"
SelectedItem="{Binding SelectedBike}"
IsEnabled="{Binding IsIdle}"
IsVisible="{Binding IsBikesListVisible}"
HasUnevenRows="True"
ItemTemplate="{StaticResource bikeTemplateSelector}"/>
<Label
IsVisible="{Binding IsNoBikesAtStationVisible}"
VerticalOptions="EndAndExpand"
Text="{Binding NoBikesAtStationText}"/>
<Label
TextType="Html"
Text="{Binding ContactSupportHintText}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ContactSupportClickedCommand}"/>
</Label.GestureRecognizers>
</Label>
<Label
IsVisible="{Binding IsLoginRequiredHintVisible}"
TextType="Html"
Text="{Binding LoginRequiredHintText}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding LoginRequiredHintClickedCommand}"/>
</Label.GestureRecognizers>
</Label>
<StackLayout
VerticalOptions="EndAndExpand"
Orientation="Horizontal">
<Label
HeightRequest="20"
Text="{Binding StatusInfoText}"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"/>
<ActivityIndicator IsRunning="{Binding IsRunning}"
IsVisible="{Binding IsRunning}"
HeightRequest="20"
VerticalOptions="CenterAndExpand"
HorizontalOptions="End">
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="40" Android="40" WinPhone="40" />
</ActivityIndicator.WidthRequest>
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color"
iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
</StackLayout>
</StackLayout>
</Frame>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,234 @@

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TINK.Model.Bike.BluetoothLock;
namespace TINK.View.BikesAtStation
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using TINK.Model;
using TINK.Services.BluetoothLock.Tdo;
using System.Collections.Generic;
using Serilog;
using TINK.Services.BluetoothLock;
using Plugin.BLE;
using TINK.ViewModel.BikesAtStation;
using TINK.ViewModel.Bikes;
using Xamarin.CommunityToolkit.Extensions;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class BikesAtStationPage : ContentPage, IViewService, IDetailPage
#else
public partial class BikesAtStationPage : ContentPage, IViewService
#endif
{
private BikesAtStationPageViewModel m_oViewModel;
#if TRYNOTBACKSTYLE
public BikesAtStationPage()
{
InitializeComponent();
var l_oModel = App.ModelRoot;
var l_oViewModel = new BikesAtStationPageViewModel(
l_oModel.BikesAtStation,
l_oModel.ActiveUser,
l_oModel.SelectedStation,
this);
BindingContext = l_oViewModel;
BikesAtStationListView.ItemsSource = l_oViewModel;
}
#else
public BikesAtStationPage()
{
}
#endif
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
protected async override void OnAppearing()
{
if (m_oViewModel != null)
{
#if BACKSTYLE
// Hide master- detail menu to force user to navigate using back button.
m_oNavigation.IsGestureEnabled = false;
#endif
// No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing();
return;
}
try
{
var model = App.ModelRoot;
// Backup synchronization context when called from GUI-thread.
var synchronizationContext = SynchronizationContext.Current;
m_oViewModel = new BikesAtStationPageViewModel(
model.ActiveUser,
App.PermissionsService,
App.BluetoothService,
Device.RuntimePlatform,
model.SelectedStation,
() => model.GetIsConnected(),
(isConnected) => model.GetConnector(isConnected),
App.GeolocationServicesContainer.Active,
model.LocksServices.Active,
model.Polling,
(url) => DependencyService.Get<IExternalBrowserService>().OpenUrl(url),
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};
}
catch (Exception exception)
{
Log.ForContext<BikesAtStationPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
return;
}
InitializeComponent();
#if BACKSTYLE
// Hide master- detail menu to force user to navigate using back button.
m_oNavigation.IsGestureEnabled = false;
#endif
BindingContext = m_oViewModel;
BikesAtStationListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
}
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
protected async override void OnDisappearing()
{
if (m_oViewModel != null)
{
// View model might be null.
await m_oViewModel?.OnDisappearing();
}
#if BACKSTYLE
if (m_oNavigation!= null)
m_oNavigation.IsGestureEnabled = true; // Enables master- detail menu navigation again when page is unloaded.
#endif
}
/// <summary> Displays alert message.</summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAlert(string title, string message, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, 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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", 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>
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEFLYOUT
/// <summary> Creates and a page an shows it.</summary>
/// <remarks> When user is not logged in navigation to Login page is supported.</remarks>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string title = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
public async Task PushModalAsync(ViewTypes typeOfPage)
=> await Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync()
{
throw new NotSupportedException();
}
public Task PushAsync(ViewTypes p_oTypeOfPage)
{
throw new NotImplementedException();
}
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
private INavigationMasterDetail m_oNavigation;
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigation = value; }
}
#endif
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace TINK.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,72 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:conv="clr-namespace:TINK.View"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.Contact.ContactPage"
Title="{x:Static resources:AppResources.MarkingContactPageTitle}">
<ContentPage.Resources>
<conv:StringNotNullOrEmptyToVisibleConverter x:Key="StringNotNullOrEmpty_Converter"/>
<conv:BoolInverterConverter x:Key="BoolInvert_Converter"/>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<Frame>
<StackLayout x:Name="ContactPageView">
<Frame
IsVisible="{Binding
Path=IsOperatorInfoAvaliable,
Converter={StaticResource BoolInvert_Converter}}">
<!-- Button to select station and explanation text -->
<StackLayout>
<Label
TextType="Html"
Text="{x:Static resources:AppResources.MarkingContactNoStationInfoAvailableNoButton}"/>
<Button
Text="{x:Static resources:AppResources.ActionSelectStation}"
Command="{Binding OnSelectStationRequest}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding IsOperatorInfoAvaliable}">
<!-- Operator info -->
<StackLayout>
<Label
IsVisible="{Binding IsOperatorInfoAvaliable}"
HorizontalOptions="Center"
FontAttributes="Bold"
Text="{Binding ProviderNameText}"/>
<!--- Mail address -->
<Label
IsVisible="{Binding MailAddressText, Converter={StaticResource StringNotNullOrEmpty_Converter}}"
FormattedText="{Binding MailAddressAndMotivationsText}"/>
<Button
x:Name="MailAddressButton"
IsVisible="{Binding MailAddressText, Converter={StaticResource StringNotNullOrEmpty_Converter}}"
Text="{Binding MailAddressText}"
IsEnabled="{Binding IsSendMailAvailable}"
Command="{Binding OnMailRequest}"/>
<!--- Mail address -->
<Label
IsVisible="{Binding PhoneNumberText, Converter={StaticResource StringNotNullOrEmpty_Converter}}"
FormattedText="{Binding PhoneContactText}"/>
<Button
x:Name="PhoneNumberButton"
IsVisible="{Binding PhoneNumberText, Converter={StaticResource StringNotNullOrEmpty_Converter}}"
Text="{Binding PhoneNumberText}"
IsEnabled="{Binding IsDoPhoncallAvailable}"
Command="{Binding OnPhoneRequest}"/>
</StackLayout>
</Frame>
<Frame>
<StackLayout>
<Label FormattedText="{Binding LikeTinkApp}"/>
<Button Text="{x:Static resources:AppResources.ActionContactRate}"
Command="{Binding OnRateRequest}"/>
</StackLayout>
</Frame>
</StackLayout>
</Frame>
</ScrollView>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,102 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Device;
using TINK.View.MasterDetail;
using TINK.ViewModel.Info;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Contact
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ContactPage : ContentPage, IViewService, IDetailPage
{
public ContactPage ()
{
InitializeComponent ();
ContactPageView.BindingContext = new ContactPageViewModel(
App.ModelRoot.SelectedStation,
App.ModelRoot.Uris.ActiveUri,
() => App.CreateAttachment(),
() => DependencyService.Get<IExternalBrowserService>().OpenUrl(DependencyService.Get<IAppInfo>().StoreUrl),
this);
}
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string title = null)
=> NavigationMasterDetail.ShowPage(p_oType.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
{
throw new NotSupportedException();
}
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync()
{
throw new NotSupportedException();
}
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes typeOfPage)
{
if (!(Activator.CreateInstance(typeOfPage.GetViewType()) is IDetailPage detailPage))
{
await Task.CompletedTask;
return;
}
// Set reference to navigation object to be able to show page on newly shown detailPage.
detailPage.NavigationMasterDetail = NavigationMasterDetail;
await Navigation.PushAsync((Page)detailPage);
}
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public INavigationMasterDetail NavigationMasterDetail { set; private get; }
#endif
}
}

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="clr-namespace:Xamarin.Forms.GoogleMaps;assembly=Xamarin.Forms.GoogleMaps"
xmlns:bindings="clr-namespace:Xamarin.Forms.GoogleMaps.Bindings;assembly=Xamarin.Forms.GoogleMaps.Bindings"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.Contact.SelectStationPage"
Title="{x:Static resources:AppResources.MarkingSelectStationPage}">
<ContentPage.Content>
<StackLayout>
<Grid
IsEnabled="{Binding IsMapPageEnabled}"
VerticalOptions="FillAndExpand">
<maps:Map WidthRequest="320" HeightRequest="800"
x:Name="MyMap"
IsShowingUser="False"
MapType="Street">
<maps:Map.Behaviors>
<bindings:BindingPinsBehavior Value="{Binding Pins}"/>
<bindings:PinClickedToCommandBehavior Command="{Binding PinClickedCommand}"/>
</maps:Map.Behaviors>
</maps:Map>
</Grid>
<StackLayout
Margin="6,3,6,6"
VerticalOptions="EndAndExpand"
Orientation="Horizontal">
<Label
HeightRequest="20"
Text="{Binding StatusInfoText}"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"/>
<ActivityIndicator IsRunning="{Binding IsRunning}"
IsVisible="{Binding IsRunning}"
HeightRequest="20"
VerticalOptions="CenterAndExpand"
HorizontalOptions="End">
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="40" Android="40" WinPhone="40" />
</ActivityIndicator.WidthRequest>
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color"
iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,207 @@
using System;
using System.Threading.Tasks;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Contact
{
using Serilog;
using TINK.ViewModel.Contact;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class SelectStationPage : ContentPage, IViewService, IDetailPage
#else
public partial class SelectStationPage : ContentPage, IViewService
#endif
{
/// <summary> View model to notify about whether page appears or hides. </summary>
private SelectStationPageViewModel SelectStationPageViewModel { get; set; }
public SelectStationPage()
{
InitializeComponent();
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAlert(string title, string message, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, 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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, 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>
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="type">Type of page to show.</param>
public void ShowPage(ViewTypes type, string title = null)
=> NavigationMasterDetail.ShowPage(type.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="typeOfPage">Type of page to display.</param>
public async Task PushModalAsync(ViewTypes typeOfPage)
=> await Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public async Task PopModalAsync()
=> await Navigation.PopModalAsync();
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes typeOfPage)
{
#if USEFLYOUT
var page = Activator.CreateInstance(typeOfPage.GetViewType()) as IDetailPage;
#else
var page = Activator.CreateInstance(typeOfPage.GetViewType());
#endif
if (page == null)
{
return;
}
#if USEFLYOUT
page.NavigationMasterDetail = NavigationMasterDetail;
#endif
await Navigation.PushAsync((Page)page);
}
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail { private get; set; }
#endif
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
protected async override void OnAppearing()
{
// Pass reference to member Navigation to show bikes at station x dialog.
try
{
Log.ForContext<SelectStationPageViewModel>().Verbose("Constructing select station view model.");
#if TRYNOTBACKSTYLE
SelectStationPageViewModel = new SelectStationPageViewModel();
#else
SelectStationPageViewModel = new SelectStationPageViewModel(
App.ModelRoot,
App.PermissionsService,
App.BluetoothService,
App.GeolocationServicesContainer.Active,
(mapspan) => MyMap.MoveToRegion(mapspan),
this,
Navigation);
#endif
}
catch (Exception exception)
{
Log.ForContext<SelectStationPageViewModel>().Error("Constructing select station view model failed. {Exception}", exception);
return;
}
try
{
BindingContext = SelectStationPageViewModel;
#if USEFLYOUT
SelectStationPageViewModel.NavigationMasterDetail = NavigationMasterDetail;
#endif
}
catch (Exception exception)
{
Log.ForContext<SelectStationPageViewModel>().Error("Setting binding/ navigaton on select station failed. {Exception}", exception);
return;
}
try
{
base.OnAppearing();
}
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<SelectStationPageViewModel>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
return;
}
try
{
// Pre move and scanle maps to avoid initial display of map in Rome.
Log.ForContext<SelectStationPageViewModel>().Verbose("Moving and scaling map.");
SelectStationPageViewModel.MoveAndScale(
(mapSpan) => MyMap.MoveToRegion(mapSpan),
App.ModelRoot.Uris.ActiveUri);
}
catch (Exception exception)
{
// Continue because a map not beeing moved/ scaled is no reason for aborting startup.
Log.ForContext<SelectStationPageViewModel>().Error("Moving and scaling map failed. {Exception}", exception);
}
try
{
Log.ForContext<SelectStationPageViewModel>().Verbose("Invoking OnAppearing on select station view model.");
await SelectStationPageViewModel.OnAppearing();
}
catch (Exception exception)
{
Log.ForContext<SelectStationPageViewModel>().Error("Invoking OnAppearing on select station view model failed. {Exception}", exception);
return;
}
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.CopriWebView.ManageAccountPage">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackLayout>
<WebView x:Name="ManageAccount"
HeightRequest="1400"
WidthRequest="1000"
Source="{Binding Uri}" />
</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,42 @@
using TINK.Model.Device;
using TINK.ViewModel.Login;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.CopriWebView
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ManageAccountPage : ContentPage
{
public ManageAccountPage ()
{
InitializeComponent ();
ManageAccount.Navigating += (sender, ev) =>
{
if (!ev.Url.ToUpper().EndsWith(".PDF"))
{
// Stay inside web view except for downloading pdf- files.
return;
}
DependencyService.Get<IExternalBrowserService>().OpenUrl(ev.Url);
};
ManageAccount.Navigated += (sender, ev) =>
{
if (ev.Result == WebNavigationResult.Success) return;
ManageAccount.Source = new HtmlWebViewSource
{
Html = "<html><b>Kann persönliche Daten nicht anzeigen/ verwalten!</b><br>Verbindung mit Internet ok?</html>"
};
};
ManageAccount.BindingContext = new ManageAccountViewModel(
App.ModelRoot.ActiveUser.SessionCookie,
Model.TinkApp.MerchantId,
App.ModelRoot.NextActiveUri.Host);
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.CopriWebView.PasswordForgottenPage">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackLayout>
<WebView x:Name="PasswordForgottenWebView"
HeightRequest="1400"
WidthRequest="1000"
Source="{Binding Uri}" />
</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,30 @@

using TINK.ViewModel.CopriWebView;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.CopriWebView
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PasswordForgottenPage : ContentPage
{
public PasswordForgottenPage ()
{
InitializeComponent ();
PasswordForgottenWebView.Navigated += (sender, ev) =>
{
if (ev.Result == WebNavigationResult.Success) return;
PasswordForgottenWebView.Source = new HtmlWebViewSource
{
Html = "<html><b>Kann Passwort vergessen Seite nicht anzeigen!</b><br>Verbindung mit Internet ok?</html>"
};
};
PasswordForgottenWebView.BindingContext = new PasswordForgottonViewModel(
Model.TinkApp.MerchantId,
App.ModelRoot.NextActiveUri.Host);
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.CopriWebView.RegisterPage">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackLayout>
<WebView x:Name="RegisterView"
HeightRequest="1400"
WidthRequest="1000"
Source="{Binding Uri}" />
</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,33 @@

using TINK.Model.Device;
using TINK.ViewModel.CopriWebView;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.CopriWebView
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RegisterPage : ContentPage
{
public RegisterPage()
{
DependencyService.Get<IWebView>().ClearCookies();
InitializeComponent();
RegisterView.Navigated += (sender, ev) =>
{
if (ev.Result == WebNavigationResult.Success) return;
RegisterView.Source = new HtmlWebViewSource
{
Html = "<html><b>Kann Anmeldeseite nicht anzeigen</b>!<br>Verbindung mit Internet ok?</html>"
};
};
RegisterView.BindingContext = new RegisterPageViewModel(
App.ModelRoot.NextActiveUri.Host);
}
}
}

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8" ?>
<xct:Popup xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:local="clr-namespace:TINK.View"
x:TypeArguments="local:FeedbackPopup+Result"
x:Class="TINK.View.FeedbackPopup">
<xct:Popup.Resources>
<x:String x:Key="check_circle">&#xf058;</x:String>
</xct:Popup.Resources>
<Grid>
<Grid.RowDefinitions>
<!-- Head and title row -->
<RowDefinition Height="auto"/>
<!--- checkbox and input elements-->
<RowDefinition Height="*"/>
<!--- ok button-->
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid
Padding="30"
BackgroundColor="{DynamicResource primary-back-title-color}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label
HorizontalTextAlignment="Center"
FontSize="Large"
Text="Fahrrad erfolgreich zurückgegeben!"/>
<Image Grid.Row="1">
<Image.Source>
<FontImageSource Size="Header" Glyph="{StaticResource check_circle}" FontFamily="FA-S"/>
</Image.Source>
</Image>
</Grid>
<ScrollView Grid.Row="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackLayout
Orientation="Vertical">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="brockenCheckBox" IsChecked="True"/>
<Label
Grid.Column="1"
FontSize="Medium"
Text="Rad ist in Ordnung"/>
</Grid>
<Editor
Grid.Row="1"
x:Name="feedbackMessage"
AutoSize="TextChanges"
Placeholder="Bei Bedarf bitte hier Rückmeldung eingeben."
Text="">
<Editor.Triggers>
<DataTrigger TargetType="Editor"
Binding="{Binding Source={x:Reference brockenCheckBox}, Path=IsChecked}"
Value="true">
<Setter Property="Placeholder"
Value="Bei Bedarf bitte hier Rückmeldung eingeben." />
</DataTrigger>
<DataTrigger TargetType="Editor"
Binding="{Binding Source={x:Reference brockenCheckBox}, Path=IsChecked}"
Value="false">
<Setter Property="Placeholder"
Value="Bitte Zustand/ Defekt hier beschreiben." />
</DataTrigger>
</Editor.Triggers>
</Editor>
</StackLayout>
</Grid>
</ScrollView>
<Button
Grid.Row="2"
Clicked="OnOkClicked"
Text="OK"/>
</Grid>
</xct:Popup>

View file

@ -0,0 +1,58 @@
using System;
using Xamarin.CommunityToolkit.UI.Views;
using Xamarin.Forms.Xaml;
namespace TINK.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FeedbackPopup : Popup<FeedbackPopup.Result>
{
public FeedbackPopup()
{
InitializeComponent();
}
protected override FeedbackPopup.Result GetLightDismissResult()
{
return new Result
{
Message = feedbackMessage.Text,
IsBikeBroken = brockenCheckBox.IsChecked
};
}
private void OnOkClicked(object sender, EventArgs eventArgs)
{
var result = new Result
{
Message = feedbackMessage.Text,
IsBikeBroken = brockenCheckBox.IsChecked
};
Dismiss(result);
}
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
#if USCSHARP9
public class Result : IViewService.IUserFeedback
#else
public new class Result : IUserFeedback
#endif
{
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
public bool IsBikeBroken { get; set; }
/// <summary>
/// Holds either
/// - general feedback
/// - error description of broken bike
/// or both.
/// </summary>
public string Message { get; set; }
}
}
}

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.Contact.FeesAndBikesPage">
<!--Pages can be added as references or inline-->
<ContentPage Title="{x:Static resources:AppResources.MarkingTabFees}">
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="InfoRentBikeWebView"
HeightRequest="1000"
WidthRequest="1000"
Source="{Binding RentBikeText}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
<ContentPage Title="{x:Static resources:AppResources.MarkingTabBikes}">
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="InfoTypesOfBikesWebView"
HeightRequest="1000"
WidthRequest="1000"
Source="{Binding TypesOfBikesText}"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
</TabbedPage>

View file

@ -0,0 +1,37 @@
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using TINK.ViewModel.Contact;
namespace TINK.View.Contact
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FeesAndBikesPage : TabbedPage
{
public HelpContactViewModel ViewModel { get; }
public FeesAndBikesPage ()
{
InitializeComponent();
ViewModel = new HelpContactViewModel(
App.ModelRoot.NextActiveUri.Host,
App.ModelRoot.IsSiteCachingOn,
resourceName => ViewModelResourceHelper.GetSource(resourceName));
BindingContext = ViewModel;
/// Info about renting.
InfoRentBikeWebView.Navigating += ViewModelHelper.OnNavigating;
/// Info about types of bikes.
InfoTypesOfBikesWebView.Navigating += ViewModelHelper.OnNavigating;
}
/// <summary> Called when page is shown. </summary>
protected override void OnAppearing()
{
ViewModel.OnAppearing();
}
}
}

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.FindBike.FindBikePage"
xmlns:local_bike="clr-namespace:TINK.View.Bike">
<ContentPage.Resources>
<ResourceDictionary>
<local_bike:BikeViewCellTemplateSelector x:Key="bikeTemplateSelector"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Frame>
<StackLayout>
<Entry
Placeholder="Fahrrad-Nummer bitte hier eingeben"
IsVisible="{Binding IsSelectBikeVisible}"
MaxLength="10"
CursorPosition="0"
Text="{Binding BikeIdUserInput}">
</Entry>
<Button
Text="Rad Wählen"
IsEnabled="{Binding IsSelectBikeEnabled}"
IsVisible="{Binding IsSelectBikeVisible}"
Command="{Binding OnSelectBikeRequest}">
</Button>
<ListView
x:Name="FindBikeListView"
SelectionMode="None"
SelectedItem="{Binding SelectedBike}"
IsEnabled="{Binding IsIdle}"
IsVisible="{Binding IsBikesListVisible}"
HasUnevenRows="True"
ItemTemplate="{StaticResource bikeTemplateSelector}"/>
<StackLayout
VerticalOptions="EndAndExpand"
Orientation="Horizontal">
<Label
HeightRequest="20"
Text="{Binding StatusInfoText}"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"/>
<ActivityIndicator IsRunning="{Binding IsRunning}"
IsVisible="{Binding IsRunning}"
HeightRequest="20"
VerticalOptions="CenterAndExpand"
HorizontalOptions="End">
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="40" Android="40" WinPhone="40" />
</ActivityIndicator.WidthRequest>
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color"
iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
</StackLayout>
</StackLayout>
</Frame>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,144 @@
using Serilog;
using System;
using System.Threading;
using System.Threading.Tasks;
using TINK.ViewModel.FindBike;
using Xamarin.CommunityToolkit.Extensions;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.FindBike
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FindBikePage : ContentPage, IViewService
{
/// <summary> Refernce to view model. </summary>
FindBikePageViewModel m_oViewModel = null;
public FindBikePage () { }
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
protected async override void OnAppearing()
{
if (m_oViewModel != null)
{
// No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing();
return;
}
try
{
var model = App.ModelRoot;
// Backup synchronization context when called from GUI-thread.
var synchronizationContext = SynchronizationContext.Current;
m_oViewModel = new FindBikePageViewModel(
model.ActiveUser,
App.PermissionsService,
App.BluetoothService,
Device.RuntimePlatform,
() => model.GetIsConnected(),
(isConnected) => model.GetConnector(isConnected),
App.GeolocationServicesContainer.Active,
model.LocksServices.Active,
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};
}
catch (Exception exception)
{
Log.ForContext<FindBikePage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Fahrrad Wählen kann nicht angezeigt werden. ${exception.Message}", "OK");
return;
}
InitializeComponent();
BindingContext = m_oViewModel;
FindBikeListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
public async Task PushModalAsync(ViewTypes typeOfPage)
=> await Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync() => throw new NotSupportedException();
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushAsync(ViewTypes p_oTypeOfPage) => throw new NotSupportedException();
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
}
}

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8" ?>
<CarouselPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.Info.BikeInfo.BikeInfoCarouselPage">
<CarouselPage.ItemTemplate>
<DataTemplate>
<ContentPage>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,40,0,0" />
</OnPlatform>
</ContentPage.Padding>
<ScrollView Orientation="Vertical">
<StackLayout>
<Label
Text="{Binding Title}"
FontSize="Medium"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
VerticalOptions="EndAndExpand"
VerticalTextAlignment="End"/>
<Image
IsVisible="{Binding IsImageVisble}"
Source="{Binding Image}"
Aspect="AspectFit"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
<Label
Text="{Binding DescriptionText}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
VerticalOptions="StartAndExpand"
VerticalTextAlignment="Start"/>
<Button
Text="Schließen"
IsVisible="{Binding IsCloseVisible}"
Command="{Binding OnCloseRequest}"
VerticalOptions="End"/>
<ProgressBar
VerticalOptions="End"
Progress="{Binding ProgressValue}"/>
</StackLayout>
</ScrollView>
</ContentPage>
</DataTemplate>
</CarouselPage.ItemTemplate>
</CarouselPage>

View file

@ -0,0 +1,128 @@
using System;
using System.Threading.Tasks;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using TINK.ViewModel.Info.BikeInfo;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Info.BikeInfo
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class BikeInfoCarouselPage : CarouselPage, IViewService, IDetailPage
#else
public partial class BikeInfoCarouselPage : CarouselPage, IViewService
#endif
{
public BikeInfoCarouselPage ()
{
InitializeComponent ();
ItemsSource = new BikeInfoViewModel(
resourceName => ImageSource.FromResource($"{ViewModelResourceHelper.RessourcePrefix}Images.{resourceName}"),
this).CarouselItems;
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
{
return await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
}
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
{
throw new NotSupportedException();
}
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync()
{
throw new NotSupportedException();
}
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushAsync(ViewTypes p_oTypeOfPage)
{
throw new NotImplementedException();
}
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
private INavigationMasterDetail m_oNavigation;
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigation = value; }
}
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -0,0 +1,40 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class InfoPage : ContentPage
{
public InfoPage ()
{
InitializeComponent ();
#if __IOS__
var resourcePrefix = "TINK.iOS.";
#endif
#if __ANDROID__
var resourcePrefix = "TINK.Droid.";
#endif
#if WINDOWS_PHONE
var resourcePrefix = "TINK.WinPhone.";
#endif
Debug.WriteLine("Using this resource prefix: " + resourcePrefix);
// note that the prefix includes the trailing period '.' that is required
var assembly = typeof(InfoPage).GetTypeInfo().Assembly;
Stream stream = assembly.GetManifestResourceStream
(resourcePrefix + "HtmlResouces.Info.html");
var l_oHtmlViewSource = new HtmlWebViewSource
{
Html = (new StreamReader(stream, Encoding.UTF8)).ReadToEnd()
};
InfoWebView.Source = l_oHtmlViewSource;
}
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Info
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class InfoPage : ContentPage
{
public InfoPage ()
{
InitializeComponent ();
}
}
}

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.Info.TabbedPageInfo"
x:Name="TabbedInfoPage">
<!--Pages can be added as references or inline-->
<ContentPage Title="App">
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="InfoLicenses"
Source="{Binding InfoLicenses}"
HeightRequest="1000"
WidthRequest="1000" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
<ContentPage Title="Datenschutz">
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="InfoDatenschutz"
Source="{Binding InfoPrivacy}"
HeightRequest="1000"
WidthRequest="1000" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
<ContentPage Title="AGB">
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="InfoABG"
Source ="{Binding InfoAgb}"
HeightRequest="1000"
WidthRequest="1000" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
<ContentPage Title="Impressum">
<ContentPage.Content>
<StackLayout>
<WebView
x:Name="InfoImpressum"
Source="{Binding InfoImpressum}"
HeightRequest="1000"
WidthRequest="1000" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
</TabbedPage>

View file

@ -0,0 +1,36 @@
using TINK.ViewModel;
using TINK.ViewModel.Info;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Info
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TabbedPageInfo : TabbedPage
{
public InfoViewModel ViewModel { get; }
public TabbedPageInfo()
{
InitializeComponent();
ViewModel = new InfoViewModel(
App.ModelRoot.NextActiveUri.Host,
App.ModelRoot.IsSiteCachingOn,
resourceName => ViewModelResourceHelper.GetSource(resourceName));
TabbedInfoPage.BindingContext = ViewModel;
InfoLicenses.Navigating += ViewModelHelper.OnNavigating;
InfoDatenschutz.Navigating += ViewModelHelper.OnNavigating;
InfoABG.Navigating += ViewModelHelper.OnNavigating;
InfoImpressum.Navigating += ViewModelHelper.OnNavigating;
}
/// <summary> Called when page is shown. </summary>
protected override void OnAppearing()
{
ViewModel.OnAppearing();
}
}
}

View file

@ -0,0 +1,35 @@
using System.Windows.Input;
using Xamarin.Forms;
namespace TINK.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,53 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.Login.LoginPage">
<ScrollView>
<Frame>
<StackLayout x:Name="LoginPageView">
<Frame>
<StackLayout>
<Label Text="{x:Static resources:AppResources.MarkingLoginEmailAddressLabel}"/>
<Entry
Placeholder="{x:Static resources:AppResources.MarkingLoginEmailAddressPlaceholder}"
Keyboard="Email"
AutomationId="mail_address_text"
x:Name="EMailEntry"
Text="{Binding MailAddress}"
IsEnabled="{Binding IsLoggedOut}"/>
<Label Text="{x:Static resources:AppResources.MarkingLoginPasswordLabel}"/>
<Entry
Placeholder="{x:Static resources:AppResources.MarkingLoginPasswordPlaceholder}"
AutomationId="password_text"
IsPassword="true"
x:Name="PasswordEntry"
Text="{Binding Password}"
IsEnabled="{Binding IsLoggedOut}"/>
<Button
Text="{x:Static resources:AppResources.ActionLoginLogin}"
AutomationId="login_button"
Command="{Binding OnLoginRequest}"
IsEnabled="{Binding IsLoginRequestAllowed}">
</Button>
<Button
Text="{x:Static resources:AppResources.ActionLoginRegister}"
AutomationId="register_button"
Command="{Binding OnRegisterRequest}"
IsVisible="{Binding IsWebViewElementsVisible}">
</Button>
<Label
IsVisible="{Binding IsRegisterTargetsInfoVisible}"
FormattedText="{Binding RegisterTargetsInfo}">
</Label>
<Button
Text="{x:Static resources:AppResources.ActionLoginPasswordForgotten}"
AutomationId="password_forgotten_button"
Command="{Binding OnPasswordForgottonRequest}">
</Button>
</StackLayout>
</Frame>
</StackLayout>
</Frame>
</ScrollView>
</ContentPage>

View file

@ -0,0 +1,122 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Device;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Login
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class LoginPage : ContentPage, IViewService, IDetailPage
#else
public partial class LoginPage : ContentPage, IViewService
#endif
{
public LoginPage ()
{
InitializeComponent ();
var l_oModel = App.ModelRoot;
#if !BACKSTYLE
var l_oViewModel = new LoginPageViewModel(
l_oModel,
(url) => DependencyService.Get< IExternalBrowserService>().OpenUrl(url),
this);
LoginPageView.BindingContext = l_oViewModel;
#else
LoginPageView.BindingContext = new LoginPageViewModel(l_oModel.ActiveUser, this, Navigation);
#endif
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public async Task PushModalAsync(ViewTypes typeOfPage)
=> await Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync()
{
throw new NotSupportedException();
}
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes p_oTypeOfPage)
{
await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
}
#if USEFLYOUT
/// <summary>
/// Delegate to perform navigation.
/// </summary>
private INavigationMasterDetail m_oNavigation;
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigation = value; }
}
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="clr-namespace:Xamarin.Forms.GoogleMaps;assembly=Xamarin.Forms.GoogleMaps"
xmlns:bindings="clr-namespace:Xamarin.Forms.GoogleMaps.Bindings;assembly=Xamarin.Forms.GoogleMaps.Bindings"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.Map.MapPage"
Title="{x:Static resources:AppResources.MarkingMapPage}">
<StackLayout>
<Grid
IsEnabled="{Binding IsMapPageEnabled}"
VerticalOptions="FillAndExpand">
<maps:Map WidthRequest="320" HeightRequest="800"
x:Name="MyMap"
IsShowingUser="False"
MapType="Street">
<maps:Map.Behaviors>
<bindings:BindingPinsBehavior Value="{Binding Pins}"/>
<bindings:PinClickedToCommandBehavior Command="{Binding PinClickedCommand}"/>
</maps:Map.Behaviors>
</maps:Map>
<Button
x:Name="TINKButton"
AutomationId ="FilterTINK_button"
Text="TINK"
Command="{Binding OnToggleKonradToTink}"
IsVisible="{Binding IsToggleVisible}"
TextColor ="{Binding TinkColor}"
VerticalOptions="Start"
HorizontalOptions="StartAndExpand"
WidthRequest="80">
</Button>
<Button
x:Name="KonradButton"
AutomationId ="FilterKonrad_button"
Text="Konrad"
Command="{Binding OnToggleTinkToKonrad}"
IsVisible="{Binding IsToggleVisible}"
TextColor="{Binding KonradColor}"
VerticalOptions="Start"
HorizontalOptions="EndAndExpand"
WidthRequest="80">
</Button>
</Grid>
<StackLayout
Margin="6,3,6,6"
VerticalOptions="EndAndExpand"
Orientation="Horizontal">
<Label
HeightRequest="20"
Text="{Binding StatusInfoText}"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"/>
<ActivityIndicator IsRunning="{Binding IsRunning}"
IsVisible="{Binding IsRunning}"
HeightRequest="20"
VerticalOptions="CenterAndExpand"
HorizontalOptions="End">
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="40" Android="40" WinPhone="40" />
</ActivityIndicator.WidthRequest>
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color"
iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
</StackLayout>
</StackLayout>
</ContentPage>

View file

@ -0,0 +1,243 @@
using System;
using System.Threading.Tasks;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Map
{
using Serilog;
using TINK.ViewModel.Map;
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class MapPage : ContentPage, IViewService, IDetailPage
#else
public partial class MapPage : ContentPage, IViewService
#endif
{
/// <summary> View model to notify about whether page appears or hides. </summary>
private MapPageViewModel MapPageViewModel { get; set; }
/// <summary>
/// Constructs map page instance.
/// </summary>
public MapPage()
{
InitializeComponent();
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="title">Title of message.</param>
/// <param name="message">Message to display.</param>
/// <param name="cancel">Type of buttons.</param>
public new async Task DisplayAlert(string title, string message, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, 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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, 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>
public new async Task<bool> DisplayAlert(string title, string message, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, message, accept, cancel);
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes type, string title = null)
=> NavigationMasterDetail.ShowPage(type.GetViewType(), title);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="typeOfPage">Type of page to display.</param>
public async Task PushModalAsync(ViewTypes typeOfPage)
=> await Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public async Task PopModalAsync()
=> await Navigation.PopModalAsync();
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes typeOfPage)
{
#if USEFLYOUT
var page = Activator.CreateInstance(typeOfPage.GetViewType()) as IDetailPage;
#else
var page = Activator.CreateInstance(p_oTypeOfPage.GetViewType());
#endif
if (page == null)
{
return;
}
#if USEFLYOUT
page.NavigationMasterDetail = NavigationMasterDetail;
#endif
await Navigation.PushAsync((Page)page);
}
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
#if USEFLYOUT
/// <summary> Delegate to perform navigation.</summary>
public INavigationMasterDetail NavigationMasterDetail { private get; set; }
#endif
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
protected async override void OnAppearing()
{
// Pass reference to member Navigation to show bikes at station x dialog.
try
{
Log.ForContext<MapPage>().Verbose("Constructing map page view model.");
#if TRYNOTBACKSTYLE
MapPageViewModel = new MapPageViewModel();
#else
MapPageViewModel = new MapPageViewModel(
App.ModelRoot,
App.PermissionsService,
App.BluetoothService,
App.GeolocationServicesContainer.Active,
(mapspan) => MyMap.MoveToRegion(mapspan),
this,
Navigation);
#endif
} catch (Exception exception)
{
Log.ForContext<MapPage>().Error("Constructing map page view model failed. {Exception}", exception);
return;
}
try
{
BindingContext = MapPageViewModel;
#if USEFLYOUT
MapPageViewModel.NavigationMasterDetail = NavigationMasterDetail;
#endif
}
catch (Exception exception)
{
Log.ForContext<MapPage>().Error("Setting binding/ navigaton on map page failed. {Exception}", exception);
return;
}
try
{
if (Device.RuntimePlatform == Device.iOS)
{
TINKButton.BackgroundColor = Color.LightGray;
TINKButton.BorderColor = Color.Black;
TINKButton.Margin = new Thickness(10, 10, 10, 10);
KonradButton.BackgroundColor = Color.LightGray;
KonradButton.BorderColor = Color.Black;
KonradButton.Margin = new Thickness(10, 10, 10, 10);
}
}
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<MapPage>().Error("IOS specific styling of map page failed. {Exception}", exception);
}
try
{
base.OnAppearing();
}
catch (Exception exception)
{
// Continue because styling is not essential.
Log.ForContext<MapPage>().Error("Invoking OnAppearing of base failed. {Exception}", exception);
return;
}
try
{
// Pre move and scanle maps to avoid initial display of map in Rome.
Log.ForContext<MapPage>().Verbose("Moving and scaling map.");
MapPageViewModel.MoveAndScale(
(mapSpan) => MyMap.MoveToRegion(mapSpan),
App.ModelRoot.Uris.ActiveUri,
App.ModelRoot.GroupFilterMapPage);
}
catch(Exception exception)
{
// Continue because a map not beeing moved/ scaled is no reason for aborting startup.
Log.ForContext<MapPage>().Error("Moving and scaling map failed. {Exception}", exception);
}
try
{
Log.ForContext<MapPage>().Verbose("Invoking OnAppearing on map page view model.");
await MapPageViewModel.OnAppearing();
}
catch (Exception exception)
{
Log.ForContext<MapPage>().Error("Invoking OnAppearing on map page view model failed. {Exception}", exception);
return;
}
}
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
protected override async void OnDisappearing()
{
if (MapPageViewModel != null)
{
// View model might be null.
await MapPageViewModel?.OnDisappearing();
}
base.OnDisappearing();
}
}
}

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local_minisurvey="clr-namespace:TINK.View.MiniSurvey.Question"
x:Class="TINK.View.MiniSurvey.MiniSurveyPage">
<ContentPage.Resources>
<ResourceDictionary>
<local_minisurvey:QuestionViewCellTemplateSelector x:Key="questionViewCellTemplateSelector"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Frame>
<StackLayout>
<Label
FontSize="Large"
FontAttributes="Bold"
Text="{Binding Title}"/>
<Label
Text="{Binding Subtitle}"/>
<ListView
x:Name="MiniSurveyListView"
HasUnevenRows="True"
ItemTemplate="{StaticResource questionViewCellTemplateSelector}">
</ListView>
<Label
Text="{Binding Footer}"/>
<Button
IsEnabled="False"
Command="{Binding OnButtonClicked}"
Text="OK"/>
</StackLayout>
</Frame>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,95 @@
using System;
using System.Threading.Tasks;
using TINK.ViewModel.MiniSurvey;
using Xamarin.CommunityToolkit.Extensions;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.MiniSurvey
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MiniSurveyPage : ContentPage, IViewService
{
public MiniSurveyPage()
{
var model = App.ModelRoot;
var vm = new MiniSurveyViewModel(
() => model.GetIsConnected(),
(isConnected) => model.GetConnector(isConnected),
this);
InitializeComponent();
BindingContext = vm;
MiniSurveyListView.ItemsSource = vm;
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushModalAsync(ViewTypes p_oTypeOfPage) => throw new NotSupportedException();
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync() => Navigation.PopModalAsync();
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushAsync(ViewTypes p_oTypeOfPage) => throw new NotSupportedException();
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
}
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.MiniSurvey.Question.CheckOneViewCell">
<ViewCell.View>
<Frame>
<StackLayout>
<Label
FontSize="Medium"
Text="{Binding QuestionText}" />
<Picker
Title="{x:Static resources:AppResources.MiniSurveyAskForAnswer}"
SelectedItem="{Binding AnswerText}"
ItemsSource="{Binding AnswersText}"/>
</StackLayout>
</Frame>
</ViewCell.View>
</ViewCell>

View file

@ -0,0 +1,14 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.MiniSurvey.Question
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CheckOneViewCell : ViewCell
{
public CheckOneViewCell ()
{
InitializeComponent ();
}
}
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
x:Class="TINK.View.MiniSurvey.Question.FreeTextViewCell">
<ViewCell.View>
<Frame>
<StackLayout>
<Label
FontSize="Medium"
Text="{Binding QuestionText}" />
<Editor
AutoSize="TextChanges"
Text="{Binding AnswerText}"
Placeholder="{x:Static resources:AppResources.MiniSurveyEnterAnswer}">
</Editor>
</StackLayout>
</Frame>
</ViewCell.View>
</ViewCell>

View file

@ -0,0 +1,15 @@

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.MiniSurvey.Question
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FreeTextViewCell : ViewCell
{
public FreeTextViewCell ()
{
InitializeComponent ();
}
}
}

View file

@ -0,0 +1,26 @@
using Xamarin.Forms;
namespace TINK.View.MiniSurvey.Question
{
/// <summary>
/// Selects different templates for different question types.
/// </summary>
public class QuestionViewCellTemplateSelector : DataTemplateSelector
{
DataTemplate checkOneViewCell;
DataTemplate freeTextViewCell;
public QuestionViewCellTemplateSelector()
{
checkOneViewCell = new DataTemplate(typeof(CheckOneViewCell));
freeTextViewCell = new DataTemplate(typeof(FreeTextViewCell));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
return item is ViewModel.MiniSurvey.Question.FreeTextViewModel
? freeTextViewCell
: checkOneViewCell;
}
}
}

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.MyBikes.MyBikesPage"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
xmlns:local_bike="clr-namespace:TINK.View.Bike"
Title="{x:Static resources:AppResources.MarkingMyBikes}">
<ContentPage.Resources>
<ResourceDictionary>
<local_bike:BikeViewCellTemplateSelector x:Key="bikeTemplateSelector"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Frame>
<StackLayout>
<ListView
x:Name="MyBikesListView"
SelectionMode="None"
SelectedItem="{Binding SelectedBike}"
IsEnabled="{Binding IsIdle}"
IsVisible="{Binding IsBikesListVisible}"
HasUnevenRows="True"
ItemTemplate="{StaticResource bikeTemplateSelector}"/>
<StackLayout
VerticalOptions="EndAndExpand"
Orientation="Horizontal">
<Label
IsVisible="{Binding IsNoBikesOccupiedVisible}"
VerticalOptions="StartAndExpand"
Text="{Binding NoBikesOccupiedText}"/>
<Label
HeightRequest="20"
Text="{Binding StatusInfoText}"
VerticalOptions="Center"
HorizontalOptions="FillAndExpand"/>
<ActivityIndicator IsRunning="{Binding IsRunning}"
IsVisible="{Binding IsRunning}"
HeightRequest="20"
VerticalOptions="CenterAndExpand"
HorizontalOptions="End">
<ActivityIndicator.WidthRequest>
<OnPlatform x:TypeArguments="x:Double" iOS="40" Android="40" WinPhone="40" />
</ActivityIndicator.WidthRequest>
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color"
iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
</StackLayout>
</StackLayout>
</Frame>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,169 @@
using Plugin.Connectivity;
using System;
using System.Threading;
using System.Threading.Tasks;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.MyBikes
{
using Serilog;
using TINK.ViewModel.MyBikes;
using Xamarin.CommunityToolkit.Extensions;
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyBikesPage : ContentPage, IViewService
{
/// <summary> Refernce to view model. </summary>
MyBikesPageViewModel m_oViewModel = null;
/// <summary>
/// Constructs a my bikes page.
/// </summary>
public MyBikesPage()
{
}
/// <summary>
/// Invoked when page is shown.
/// Starts update process.
/// </summary>
protected async override void OnAppearing()
{
if (m_oViewModel != null)
{
// No need to create view model, set binding context an items source if already done.
// If done twice tap events are fired multiple times (when hiding page using home button).
await m_oViewModel.OnAppearing();
return;
}
try
{
var model = App.ModelRoot;
// Backup synchronization context when called from GUI-thread.
var synchronizationContext = SynchronizationContext.Current;
m_oViewModel = new MyBikesPageViewModel(
model.ActiveUser,
App.PermissionsService,
App.BluetoothService,
Device.RuntimePlatform,
() => model.GetIsConnected(),
(isConnected) => model.GetConnector(isConnected),
App.GeolocationServicesContainer.Active,
model.LocksServices.Active,
model.Polling,
(d, obj) => synchronizationContext.Post(d, obj),
model.SmartDevice,
this)
{
IsReportLevelVerbose = model.IsReportLevelVerbose
};
}
catch (Exception exception)
{
Log.ForContext<MyBikesPage>().Error("Displaying bikes at station page failed. {Exception}", exception);
await DisplayAlert("Fehler", $"Seite Räder an Station kann nicht angezeigt werden. ${exception.Message}", "OK");
return;
}
InitializeComponent();
BindingContext = m_oViewModel;
MyBikesListView.ItemsSource = m_oViewModel;
await m_oViewModel.OnAppearing();
}
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
protected async override void OnDisappearing()
{
if (m_oViewModel == null)
{
// View model might be null (Example: Occured when page to querry for location permissions was opened)
return;
}
await m_oViewModel.OnDisappearing();
}
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary>
/// Displays alert message.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="typeOfPage">Page to display.</param>
public async Task PushModalAsync(ViewTypes typeOfPage)
=> await Navigation.PushModalAsync((Page)Activator.CreateInstance(typeOfPage.GetViewType()));
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync() => throw new NotSupportedException();
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushAsync(ViewTypes p_oTypeOfPage) => throw new NotSupportedException();
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
}
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<FlyoutPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.Root.RootPage"
xmlns:pages="clr-namespace:TINK.View.Root"
xmlns:mappage="clr-namespace:TINK.View.Map">
<FlyoutPage.Flyout>
<pages:RootPageFlyout x:Name="FlyoutPage" />
</FlyoutPage.Flyout>
<FlyoutPage.Detail>
<NavigationPage>
<x:Arguments>
<mappage:MapPage />
</x:Arguments>
</NavigationPage>
</FlyoutPage.Detail>
</FlyoutPage>

View file

@ -0,0 +1,95 @@
using System;
using TINK.Model.Station;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Root
{
/// <summary>
/// Mamages creation of detail pages if a flyout page menu entry is selected.
/// Exposes flyout page style navigation which is used by detail pages.
/// </summary>
/// <remarks>
/// Examples of use cases when detail pages do navigation:
// - switch to map page after succesfully logging in/ logging out
// - switch to login page form bikes at station page if not yet logged in
/// </remarks>
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class RootPage : FlyoutPage, INavigationMasterDetail
#else
public partial class RootPage : FlyoutPage
#endif
{
public RootPage()
{
InitializeComponent();
FlyoutPage.ListView.ItemSelected += OnListViewItemSelected;
// Any type of split behaviour conflics with map shifting functionality (assuming FlyoutPage behaves same like MasterDetailPage).
FlyoutLayoutBehavior = FlyoutLayoutBehavior.Popover;
var navigationPage = Detail as NavigationPage;
if (navigationPage == null)
{
return;
}
#if USEFLYOUT
var detailPage = navigationPage.RootPage as IDetailPage;
if (detailPage == null)
{
return;
}
detailPage.NavigationMasterDetail = this;
#endif
}
/// <summary>
/// Is called if a flyout page menu entry is selected.
/// Creates a new page.
/// </summary>
private void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (!(e.SelectedItem is RootPageFlyoutMenuItem item))
{
// Unexpected argument detected.
return;
}
// Set selected station to new
App.ModelRoot.SelectedStation = new NullStation();
ShowPage(item.TargetType, item.Title);
IsPresented = false;
FlyoutPage.ListView.SelectedItem = null;
}
/// <summary>
/// Shows a detail page.
/// </summary>
/// <param name="typeOfPage">Type of page to show.</param>
/// <param name="title">Title of page.</param>
public void ShowPage(Type typeOfPage, string title)
{
var page = (Page)Activator.CreateInstance(typeOfPage);
page.Title = title;
#if USEFLYOUT
if (page is IDetailPage detailPage)
{
// Detail page needs reference to perform navigation.
// Examples see above in xdoc of class.
detailPage.NavigationMasterDetail = this;
}
#endif
Detail = new NavigationPage(page);
}
}
}

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.Root.RootPageFlyout"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
xmlns:local="clr-namespace:TINK.View"
IconImageSource="menu"
Title="sharee.bike">
<StackLayout>
<ListView x:Name="MenuItemsListView"
SeparatorVisibility="None"
HasUnevenRows="true"
local:ListViewAttachedBehavior.Command="{Binding MenuItemSelected}"
ItemsSource="{Binding MenuItems}">
<ListView.Header>
<Grid BackgroundColor="{DynamicResource Key=primary-back-title-color}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="80"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Label
Grid.Column="1"
Grid.Row="2"
Text="{Binding MasterDetailMenuTitlte}"
Style="{DynamicResource SubtitleStyle}"/>
</Grid>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="5,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image>
<Image.Source>
<FontImageSource Glyph="{Binding GlyphCode}" Color="Black" FontFamily="FA-S"/>
</Image.Source>
</Image>
<Label
Grid.Column="1"
Margin="10, 0, 0, 0"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center"
FontSize="18"
Text="{Binding Title}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>

View file

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Services;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using TINK.View.Themes;
using TINK.ViewModel.Root;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.Root
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class RootPageFlyout : ContentPage
{
public ListView ListView;
public RootPageFlyout()
{
InitializeComponent();
BindingContext = new RootPageFlyoutViewModel();
ListView = MenuItemsListView;
}
}
}

View file

@ -0,0 +1,20 @@
using System;
namespace TINK.View.Root
{
public class RootPageFlyoutMenuItem
{
public RootPageFlyoutMenuItem()
{
TargetType = typeof(RootPageFlyoutMenuItem);
}
public int Id { get; set; }
public string GlyphCode { get; set; }
public string Title { get; set; }
public Type TargetType { get; set; }
}
}

View file

@ -0,0 +1,42 @@
using System;
using TINK.View.Map;
using TINK.ViewModel.MasterDetail;
namespace TINK.View
{
public class MainPageMenuItem
{
public MainPageMenuItem()
{
TargetType = typeof(MapPage);
}
public MainPageMenuItem(
int p_iId,
Type p_oTypeOfPage,
string p_strTitle = null)
{
TargetType = p_oTypeOfPage;
Id = p_iId;
Title = p_strTitle ?? Helper.GetCaption(p_oTypeOfPage);
}
public int Id
{
get;
private set;
}
public string Title
{
get;
private set;
}
public Type TargetType
{
get;
private set;
}
}
}

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:resources="clr-namespace:TINK.MultilingualResources;assembly=TINKLib"
xmlns:mappage="clr-namespace:TINK.View.Map"
xmlns:findbike="clr-namespace:TINK.View.FindBike"
xmlns:mybikes="clr-namespace:TINK.View.MyBikes"
xmlns:account="clr-namespace:TINK.View.Account"
xmlns:login="clr-namespace:TINK.View.Login"
xmlns:settings="clr-namespace:TINK.View.Settings"
xmlns:contact="clr-namespace:TINK.View.Contact"
xmlns:info="clr-namespace:TINK.View.Info"
xmlns:header="clr-namespace:TINK.View.RootShell"
BackgroundColor="{DynamicResource Key=primary-back-title-color}"
Title="Shell"
x:Class="TINK.View.RootShell.AppShell">
<Shell.FlyoutHeader>
<header:FlyoutHeader/>
</Shell.FlyoutHeader>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingMapPage}"
Route="MapPage"
ContentTemplate="{DataTemplate mappage:MapPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconMap}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingFindBike}"
IsVisible="{Binding IsFindBikePageVisible}"
ContentTemplate="{DataTemplate findbike:FindBikePage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconFindBike}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingMyBikes}"
IsVisible="{Binding IsMyBikesPageVisible}"
ContentTemplate="{DataTemplate mybikes:MyBikesPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconMyBikes}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingAccount}"
IsVisible="{Binding IsAccountPageVisible}"
ContentTemplate="{DataTemplate account:AccountPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconAccount}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingLogin}"
Route="LoginPage"
IsVisible="{Binding IsLoginPageVisible}"
ContentTemplate="{DataTemplate login:LoginPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconLogin}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingSettings}"
IsVisible="{Binding IsSettingsPageVisible}"
ContentTemplate="{DataTemplate settings:SettingsPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconSettings}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingFeesAndBikes}"
ContentTemplate="{DataTemplate contact:FeesAndBikesPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconFeesAndBikes}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{x:Static resources:AppResources.MarkingFeedbackAndContact}"
ContentTemplate="{DataTemplate contact:ContactPage}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconContact}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="{Binding TabbedPageIngoTitle}"
ContentTemplate="{DataTemplate info:TabbedPageInfo}">
<ShellContent.FlyoutIcon>
<FontImageSource Glyph="{StaticResource IconInfo}" Color="Black" FontFamily="FA-S" />
</ShellContent.FlyoutIcon>
</ShellContent>
</FlyoutItem>
</Shell>

View file

@ -0,0 +1,15 @@

using TINK.ViewModel.RootShell;
using Xamarin.Forms;
namespace TINK.View.RootShell
{
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
}
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
BackgroundColor="{DynamicResource Key=primary-back-title-color}"
x:Class="TINK.View.RootShell.FlyoutHeader">
<Grid Padding="5,10">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Image
HeightRequest="140"
Aspect="AspectFit"
Source="sharee_no_background.png" />
<Label
HorizontalOptions="CenterAndExpand"
FontSize="Large"
Grid.Row="1"
Text="{Binding MasterDetailMenuTitlte}"/>
</Grid>
</ContentView>

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.RootShell
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class FlyoutHeader : ContentView
{
public FlyoutHeader ()
{
InitializeComponent ();
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Xamarin.Forms;
using TINK.Model.User.Account;
using System.Linq;
using System.Reflection;
namespace TINK.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 AnyPermissionToVisibleConverter : 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)
{
return ((Permissions)(value)) != Permissions.None;
}
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 Xamarin.Forms;
using TINK.Model.User.Account;
namespace TINK.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,230 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.Settings.SettingsPage"
xmlns:conv="clr-namespace:TINK.View.Settings">
<ContentPage.Resources>
<conv:AnyPermissionToVisibleConverter x:Key="Frame_Converter"/>
<conv:PermissionToVisibleConverter x:Key="PickCopriServer_Converter" VisibleFlag="PickCopriServer"/>
<conv:PermissionToVisibleConverter x:Key="ManagePolling_Converter" VisibleFlag="ManagePolling"/>
<conv:PermissionToVisibleConverter x:Key="ManageCopriCacheExpiration_Converter" VisibleFlag="ManageCopriCacheExpiration"/>
<conv:PermissionToVisibleConverter x:Key="PickLockServiceImplementation_Converter" VisibleFlag="PickLockServiceImplementation"/>
<conv:PermissionToVisibleConverter x:Key="PickLocationServiceImplementation_Converter" VisibleFlag="PickLocationServiceImplementation"/>
<conv:PermissionToVisibleConverter x:Key="PickLoggingLevel_Converter" VisibleFlag="PickLoggingLevel"/>
<conv:PermissionToVisibleConverter x:Key="ReportLevel_Converter" VisibleFlag="ReportLevel"/>
<conv:PermissionToVisibleConverter x:Key="ShowDiagnostics_Converter" VisibleFlag="ShowDiagnostics"/>
<conv:PermissionToVisibleConverter x:Key="SwitchSiteCaching_Converter" VisibleFlag="SwitchNoSiteCaching"/>
</ContentPage.Resources>
<ContentPage.Content>
<ScrollView>
<Frame>
<StackLayout>
<!--
<Button Text="Feedback" Clicked="OnFeedbackClickedAsync"/>
-->
<Frame>
<StackLayout>
<Label Text="Karte auf aktuelle Position ausrichten"/>
<Switch IsToggled="{Binding CenterMapToCurrentLocation}"/>
</StackLayout>
</Frame>
<!-- Filter on view TINK/ Konrad -->
<Frame IsVisible="{Binding IsGroupFilterVisible}">
<StackLayout>
<Label Text="Ausblenden/ Einblenden"/>
<ListView
HasUnevenRows="True"
HeightRequest="120"
x:Name="Filters">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="{Binding IsEnabled}">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Text}"/>
<Switch IsToggled="{Binding IsActivated}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<StackLayout>
<!-- Themes -->
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
Text="Theme"/>
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
ItemsSource="{Binding Themes.ServicesTextList}"
SelectedItem="{Binding Themes.ActiveText}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- COPRI server selection -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
Text="{Binding CopriServerUriList.CorpiServerUriDescription}"/>
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickCopriServer_Converter}}"
ItemsSource="{Binding CopriServerUriList.ServerTextList}"
SelectedItem="{Binding CopriServerUriList.NextActiveServerText}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ManagePolling_Converter}}"
Text="{Binding Polling.PollingText}"/>
<Switch
IsVisible="{Binding DebugLevel, Converter={StaticResource ManagePolling_Converter}}"
IsToggled="{Binding Polling.IsActivated}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ManagePolling_Converter}}"
Text="{Binding Polling.PeriodeTotalSecondsText}"/>
<Stepper
IsVisible="{Binding DebugLevel, Converter={StaticResource ManagePolling_Converter}}"
Minimum="5"
Increment="5"
Maximum="600"
IsEnabled="{Binding Polling.IsActivated}"
Value="{Binding Polling.PeriodeTotalSeconds}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ManageCopriCacheExpiration_Converter}}"
Text="Time after which COPRI-cache expires [s]"/>
<Slider
IsVisible="{Binding DebugLevel, Converter={StaticResource ManageCopriCacheExpiration_Converter}}"
x:Name="expiresAfter"
Minimum="0"
Maximum="15"
Value="{Binding ExpiresAfterTotalSeconds}"/>
<Entry
IsVisible="{Binding DebugLevel, Converter={StaticResource ManageCopriCacheExpiration_Converter}}"
IsReadOnly="True"
Text="{Binding ExpiresAfterTotalSecondsText}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Lock control -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLockServiceImplementation_Converter}}"
Text="Lock Control" />
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLockServiceImplementation_Converter}}"
ItemsSource="{Binding LocksServices.Services.ServicesTextList}"
SelectedItem="{Binding LocksServices.Services.ActiveText}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLockServiceImplementation_Converter}}"
Text="Bluetooth Connect Timeout [sec]"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLockServiceImplementation_Converter}}"
Text="{Binding LocksServices.ConnectTimeoutSecText}"/>
<Stepper
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLockServiceImplementation_Converter}}"
Minimum="0.1"
Increment="0.25"
Maximum="60"
Value="{Binding LocksServices.ConnectTimeoutSec}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Geolocation -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLocationServiceImplementation_Converter}}"
Text="Geolocation Control" />
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLocationServiceImplementation_Converter}}"
ItemsSource="{Binding GeolocationServices.ServicesTextList}"
SelectedItem="{Binding GeolocationServices.ActiveText}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Web site caching -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource SwitchSiteCaching_Converter}}"
Text="Caching von Websiten."/>
<Switch
IsVisible="{Binding DebugLevel, Converter={StaticResource SwitchSiteCaching_Converter}}"
IsToggled="{Binding IsSiteCachingOnDisplayValue}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Logging -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLoggingLevel_Converter}}"
Text="Logging level" />
<Picker
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLoggingLevel_Converter}}"
ItemsSource="{Binding LoggingLevels}"
SelectedItem="{Binding SelectedLoggingLevel}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLoggingLevel_Converter}}"
Text="Logdatei in externen Pfad schreiben"/>
<Switch
IsVisible="{Binding DebugLevel, Converter={StaticResource PickLoggingLevel_Converter}}"
IsToggled="{Binding LogToExternalFolderDisplayValue}"
IsEnabled="{Binding IsLogToExternalFolderVisible}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Logging -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ReportLevel_Converter}}"
Text="Verbose error messages" />
<Switch
IsVisible="{Binding DebugLevel, Converter={StaticResource ReportLevel_Converter}}"
IsToggled="{Binding IsReportLevelVerbose}"/>
</StackLayout>
</Frame>
<Frame
IsVisible="{Binding DebugLevel, Converter={StaticResource Frame_Converter}}">
<!-- Display of parameters -->
<StackLayout>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
Text="Device Identifier" />
<Entry
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
IsEnabled="false"
Text="{Binding DeviceIdentifier}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
Text="Copri Sitzungkeks"/>
<Entry
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
IsEnabled="false"
Text="{Binding SessionCookie}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
Text="Interner Pfad (Einstell./ ggf. Logging)"/>
<Editor
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
IsEnabled="false"
Text="{Binding InternalPath}"/>
<Label
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
Text="Externer Pfad (Mock/ ggf. Logging)"/>
<Editor
IsVisible="{Binding DebugLevel, Converter={StaticResource ShowDiagnostics_Converter}}"
IsEnabled="false"
Text="{Binding ExternalPath}"/>
</StackLayout>
</Frame>
</StackLayout>
</Frame>
</ScrollView>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,164 @@
using TINK.ViewModel;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
#if USEFLYOUT
using TINK.View.MasterDetail;
#endif
using System;
using TINK.Model.Device;
using Xamarin.CommunityToolkit.Extensions;
namespace TINK.View.Settings
{
[XamlCompilation(XamlCompilationOptions.Compile)]
#if USEFLYOUT
public partial class SettingsPage : ContentPage, IViewService, IDetailPage
#else
public partial class SettingsPage : ContentPage, IViewService
#endif
{
/// <summary> Refernce to view model. </summary>
SettingsPageViewModel m_oViewModel = null;
/// <summary> Constructs a settings page. </summary>
public SettingsPage ()
{
InitializeComponent ();
var l_oModel = App.ModelRoot;
m_oViewModel = new SettingsPageViewModel(
l_oModel,
App.GeolocationServicesContainer,
this);
BindingContext = m_oViewModel;
Filters.ItemsSource = m_oViewModel.GroupFilter;
}
/// <summary> Displays alert message. </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Type of buttons.</param>
public new async Task DisplayAlert(string p_strTitle, string p_strMessage, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strCancel);
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary> Displays alert message.</summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strAccept">Text of accept button.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <returns>True if user pressed accept.</returns>
public new async Task<bool> DisplayAlert(string p_strTitle, string p_strMessage, string p_strAccept, string p_strCancel)
=> await App.Current.MainPage.DisplayAlert(p_strTitle, p_strMessage, p_strAccept, p_strCancel);
/// <summary>
/// Displays an action sheet.
/// </summary>
/// <param name="p_strTitle">Title of message.</param>
/// <param name="p_strMessage">Message to display.</param>
/// <param name="p_strCancel">Text of button.</param>
/// <param name="destruction"></param>
/// <param name="p_oButtons">Buttons holding options to select.</param>
/// <returns>Text selected</returns>
public new async Task<string> DisplayActionSheet(String p_strTitle, String p_strCancel, String destruction, params String[] p_oButtons)
=> await base.DisplayActionSheet(p_strTitle, p_strCancel, destruction, p_oButtons);
#if USEFLYOUT
/// <summary>
/// Creates and a page an shows it.
/// </summary>
/// <param name="p_oTypeOfPage">Type of page to show.</param>
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> m_oNavigation.ShowPage(p_oType.GetViewType(), p_strTitle);
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
/// <summary> Pushes a page onto the modal stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
=> throw new NotImplementedException();
/// <summary> Pops a page from the modal stack. </summary>
public Task PopModalAsync()
=> throw new NotSupportedException();
#if USEFLYOUT
/// <summary>Delegate to perform navigation.</summary>
private INavigationMasterDetail m_oNavigation;
/// <summary>
/// Delegate to perform navigation.
/// </summary>
public INavigationMasterDetail NavigationMasterDetail
{
set { m_oNavigation = value; }
}
#endif
/// <summary>
/// Invoked when pages is closed/ hidden.
/// Stops update process.
/// </summary>
protected async override void OnDisappearing()
{
if (m_oViewModel == null)
{
// View model might be null.
return;
}
await m_oViewModel?.OnDisappearing();
}
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public async Task PushAsync(ViewTypes p_oTypeOfPage)
=> await Navigation.PushAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
#if USCSHARP9
public async Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#else
public async Task<IUserFeedback> DisplayUserFeedbackPopup() => await Navigation.ShowPopupAsync<FeedbackPopup.Result>(new FeedbackPopup());
#endif
#if USERFEEDBACKDLG_TRYOUT
public async void OnFeedbackClickedAsync(object sender, EventArgs ev)
{
var result = await DisplayUserFeedbackPopup();
DisplayAlert(
"Title",
$"Bike broken: {result.IsBikeBroken}. Message: {result.Message}.",
"OK");
}
#endif
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Globalization;
using Xamarin.Forms;
namespace TINK.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,77 @@
using System;
using TINK.View.Contact;
using TINK.View.CopriWebView;
using TINK.View.Info;
using TINK.View.Info.BikeInfo;
using TINK.View.Login;
using TINK.View.Map;
using TINK.View.MyBikes;
using TINK.View.Settings;
using TINK.View.WhatsNew;
using TINK.View.WhatsNew.Agb;
using TINK.View.BikesAtStation;
using Xamarin.Forms;
using TINK.View.MiniSurvey;
namespace TINK.View
{
public static class ViewTypesTypeProvider
{
public static Type GetViewType(this ViewTypes viewType)
{
switch (viewType)
{
case ViewTypes.LoginPage:
return typeof(LoginPage);
case ViewTypes.MapPage:
return typeof(MapPage);
case ViewTypes.RegisterPage:
return typeof(RegisterPage);
case ViewTypes.PasswordForgottenPage:
return typeof(PasswordForgottenPage);
case ViewTypes.BikeInfoCarouselPage:
return typeof(BikeInfoCarouselPage);
case ViewTypes.MyBikesPage:
return typeof(MyBikesPage);
case ViewTypes.SettingsPage:
return typeof(SettingsPage);
case ViewTypes.TabbedPageInfo:
return typeof(TabbedPageInfo);
case ViewTypes.FeesAndBikesPage:
return typeof(FeesAndBikesPage);
case ViewTypes.ManageAccountPage:
return typeof(ManageAccountPage);
case ViewTypes.AgbPage:
return typeof(AgbPage);
case ViewTypes.WhatsNewPage:
return typeof(WhatsNewPage);
case ViewTypes.BikesAtStation:
return typeof(BikesAtStationPage);
case ViewTypes.ContactPage:
return typeof(ContactPage);
case ViewTypes.SelectStationPage:
return typeof(SelectStationPage);
case ViewTypes.MiniSurvey:
return typeof(MiniSurveyPage);
default:
return typeof(ContentPage);
}
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.WhatsNew.Agb.AgbPage">
<ContentPage.Content>
<Frame>
<StackLayout>
<WebView
HeightRequest="1000"
WidthRequest="500"
Source="{Binding InfoAgb}"/>
<Button Text="OK"
Command="{Binding OnOk}"/>
</StackLayout>
</Frame>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,81 @@
using System;
using System.Threading.Tasks;
using TINK.ViewModel;
using TINK.ViewModel.WhatsNew.Agb;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.WhatsNew.Agb
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AgbPage : ContentPage, IViewService
{
public AgbPage ()
{
InitializeComponent ();
agbViewModel = new AgbViewModel(
App.ModelRoot.NextActiveUri.Host,
App.ModelRoot.IsSiteCachingOn,
(resourceName) => ViewModelResourceHelper.GetSource(resourceName),
this);
BindingContext = agbViewModel;
}
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
/// <summary> Invoked when page is shown. </summary>
protected async override void OnAppearing()
=> await agbViewModel.OnAppearing();
/// <summary> Reference to view model.</summary>
AgbViewModel agbViewModel;
public async Task PopModalAsync()
=> await Navigation.PopModalAsync();
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushAsync(ViewTypes p_oTypeOfPage)
=> throw new NotImplementedException();
public Task PushModalAsync(ViewTypes p_oTypeOfPage)
=> throw new NotImplementedException();
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
}
}

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TINK.View.WhatsNew.WhatsNewPage">
<ContentPage.Content>
<ScrollView>
<Frame>
<StackLayout>
<Label Text="{Binding WhatsNewTitle}"
FontSize="Large"
VerticalOptions="Start"
HorizontalOptions="CenterAndExpand" />
<Frame>
<StackLayout>
<Label
Text="{Binding WhatsNewText}"
TextType="Html"/>
<Label FormattedText="{Binding AgbChangedText}"
IsVisible="{Binding IsAgbChangedVisible}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnShowAgbTapped}"/>
</Label.GestureRecognizers>
</Label>
</StackLayout>
</Frame>
<Frame IsVisible="{Binding IsFeedbackVisible}">
<StackLayout>
<Label Text="Gibts Verbesserungsvorschläge/ Probleme mit der App?"/>
<Button Text="Verbesserungsmail schreiben"/>
<Label Text="Die App läuft rund und tut was sie soll? Über eine Bewertung würden wir uns sehr freuen!"/>
<Button Text="Bewertung abgeben"/>
</StackLayout>
</Frame>
<Button
Text="OK"
Command="{Binding OnOk}"/>
</StackLayout>
</Frame>
</ScrollView>
</ContentPage.Content>
</ContentPage>

View file

@ -0,0 +1,105 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Device;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TINK.View.WhatsNew
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WhatsNewPage : ContentPage, IViewService
{
/// <summary> Holds a reference on the view model. </summary>
private ViewModel.WhatsNew.WhatsNewViewModel WhatsNewViewModel;
/// <summary> Constructs whats new page.</summary>
/// <param name="showMasterDetail">Action to invoke master detail page.</param>
public WhatsNewPage(Action showMasterDetail)
{
InitializeComponent();
WhatsNewViewModel = new ViewModel.WhatsNew.WhatsNewViewModel(
DependencyService.Get<IAppInfo>().Version,
App.ModelRoot.WhatsNew.WhatsNewText,
App.ModelRoot.WhatsNew.IsShowAgbRequired,
showMasterDetail,
this);
BindingContext = WhatsNewViewModel;
}
/// <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">Type of buttons.</param>
public async Task DisplayAdvancedAlert(
string title,
string message,
string details,
string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", cancel);
/// <summary> Displays detailed 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 cancel button.</param>
/// <returns>True if user pressed accept.</returns>
public async Task<bool> DisplayAdvancedAlert(string title, string message, string details, string accept, string cancel)
=> await App.Current.MainPage.DisplayAlert(title, $"{message}\r\nDetails:\r\n{details}", accept, cancel);
public Task PopModalAsync()
{
throw new NotImplementedException(); ;
}
/// <summary> Pushes a page onto the stack. </summary>
/// <param name="p_oTypeOfPage">Page to display.</param>
public Task PushAsync(ViewTypes p_oTypeOfPage)
{
throw new NotImplementedException();
}
public async Task PushModalAsync(ViewTypes p_oTypeOfPage)
{
await Navigation.PushModalAsync((Page)Activator.CreateInstance(p_oTypeOfPage.GetViewType()));
}
#if USEFLYOUT
public void ShowPage(ViewTypes p_oType, string p_strTitle = null)
=> throw new NotImplementedException();
#else
/// <summary> Shows a page.</summary>
/// <param name="route">Route of the page to show.</param>
public async Task ShowPage(string route) => await Shell.Current.GoToAsync(route);
#endif
#if USCSHARP9
public Task<IViewService.IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#else
public Task<IUserFeedback> DisplayUserFeedbackPopup() => throw new NotSupportedException();
#endif
/// <summary>
/// Invoked when pages is closed/ hidden.
/// /// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
if (WhatsNewViewModel == null)
{
// View model might be null.
return;
}
WhatsNewViewModel?.OnDisappearing(() =>
{
App.ModelRoot.SetWhatsNewWasShown();
App.ModelRoot.Save();
});
}
}
}