feature: add flower
This commit is contained in:
@ -1,13 +1,20 @@
|
||||
using Blahblah.FlowerApp.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Blahblah.FlowerApp.Extensions;
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
public class AppContentPage : ContentPage, ILoggerContent
|
||||
public abstract class AppContentPage : ContentPage, ILoggerContent
|
||||
{
|
||||
public ILogger Logger { get; init; } = null!;
|
||||
public ILogger Logger { get; } = null!;
|
||||
|
||||
public FlowerDatabase Database { get; init; } = null!;
|
||||
public FlowerDatabase Database { get; } = null!;
|
||||
|
||||
protected AppContentPage(FlowerDatabase database, ILogger logger)
|
||||
{
|
||||
Database = database;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
protected T GetValue<T>(BindableProperty property)
|
||||
{
|
||||
@ -17,7 +24,7 @@ public class AppContentPage : ContentPage, ILoggerContent
|
||||
bool hasLoading = true;
|
||||
ContentView? loading;
|
||||
|
||||
#if __IOS__
|
||||
#if IOS
|
||||
private async Task DoLoading(bool flag)
|
||||
#else
|
||||
private Task DoLoading(bool flag)
|
||||
@ -38,7 +45,7 @@ public class AppContentPage : ContentPage, ILoggerContent
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
#if __IOS__
|
||||
#if IOS
|
||||
loading.IsVisible = true;
|
||||
await loading.FadeTo(1, easing: Easing.CubicOut);
|
||||
#else
|
||||
@ -48,7 +55,7 @@ public class AppContentPage : ContentPage, ILoggerContent
|
||||
}
|
||||
else
|
||||
{
|
||||
#if __IOS__
|
||||
#if IOS
|
||||
await loading.FadeTo(0, easing: Easing.CubicIn);
|
||||
loading.IsVisible = false;
|
||||
#else
|
||||
@ -57,7 +64,7 @@ public class AppContentPage : ContentPage, ILoggerContent
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if __ANDROID__
|
||||
#if ANDROID
|
||||
return Task.CompletedTask;
|
||||
#endif
|
||||
}
|
||||
@ -79,4 +86,141 @@ public class AppContentPage : ContentPage, ILoggerContent
|
||||
});
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
async Task<Location?> GetLastLocationAsyncInternal()
|
||||
{
|
||||
try
|
||||
{
|
||||
var location = await Geolocation.Default.GetLastKnownLocationAsync();
|
||||
return location;
|
||||
}
|
||||
catch (FeatureNotSupportedException fnsEx)
|
||||
{
|
||||
this.LogError(fnsEx, $"Not supported on device, {fnsEx.Message}.");
|
||||
}
|
||||
catch (FeatureNotEnabledException fneEx)
|
||||
{
|
||||
this.LogError(fneEx, $"Not enabled on device, {fneEx.Message}.");
|
||||
}
|
||||
catch (PermissionException)
|
||||
{
|
||||
this.LogWarning($"User denied.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, $"Error occurs while getting cached location, {ex.Message}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Task<Location?> GetLastLocationAsync()
|
||||
{
|
||||
if (MainThread.IsMainThread)
|
||||
{
|
||||
return GetLastLocationAsyncInternal();
|
||||
}
|
||||
var source = new TaskCompletionSource<Location?>();
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
var location = await GetLastLocationAsyncInternal();
|
||||
source.TrySetResult(location);
|
||||
});
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
TaskCompletionSource<Location?>? locationTaskSource;
|
||||
CancellationTokenSource? locationCancellationTokenSource;
|
||||
|
||||
async Task<Location?> GetCurrentLocationAsyncInternal()
|
||||
{
|
||||
if (locationTaskSource == null)
|
||||
{
|
||||
locationTaskSource = new TaskCompletionSource<Location?>();
|
||||
|
||||
try
|
||||
{
|
||||
var request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(10));
|
||||
#if IOS
|
||||
request.RequestFullAccuracy = true;
|
||||
#endif
|
||||
|
||||
locationCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var location = await Geolocation.Default.GetLocationAsync(request, locationCancellationTokenSource.Token);
|
||||
locationTaskSource.SetResult(location);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, $"Error occurs while getting current location, {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return await locationTaskSource.Task;
|
||||
}
|
||||
|
||||
protected Task<Location?> GetCurrentLocationAsync()
|
||||
{
|
||||
if (MainThread.IsMainThread)
|
||||
{
|
||||
return GetCurrentLocationAsyncInternal();
|
||||
}
|
||||
var source = new TaskCompletionSource<Location?>();
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
var location = await GetCurrentLocationAsyncInternal();
|
||||
source.TrySetResult(location);
|
||||
});
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
protected void CancelRequestLocation()
|
||||
{
|
||||
if (locationCancellationTokenSource?.IsCancellationRequested == false)
|
||||
{
|
||||
locationCancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
async Task<FileResult?> TakePhotoInternal()
|
||||
{
|
||||
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
|
||||
|
||||
if (status == PermissionStatus.Denied)
|
||||
{
|
||||
await this.AlertError(L("needCameraPermission", "Flower Story needs access to the camera to take photos."));
|
||||
#if IOS
|
||||
var settingsUrl = UIKit.UIApplication.OpenSettingsUrlString;
|
||||
await Launcher.TryOpenAsync(settingsUrl);
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
status = await Permissions.RequestAsync<Permissions.Camera>();
|
||||
}
|
||||
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var file = await MediaPicker.Default.CapturePhotoAsync();
|
||||
return file;
|
||||
}
|
||||
|
||||
protected Task<FileResult?> TakePhoto()
|
||||
{
|
||||
if (MainThread.IsMainThread)
|
||||
{
|
||||
return TakePhotoInternal();
|
||||
}
|
||||
var source = new TaskCompletionSource<FileResult?>();
|
||||
MainThread.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
var file = await TakePhotoInternal();
|
||||
source.TrySetResult(file);
|
||||
});
|
||||
return source.Task;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Blahblah.FlowerApp;
|
||||
|
||||
internal class VisibleIfNotNullConverter : IValueConverter
|
||||
class VisibleIfNotNullConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
@ -18,3 +18,24 @@ internal class VisibleIfNotNullConverter : IValueConverter
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DateTimeStringConverter : IValueConverter
|
||||
{
|
||||
public string Format { get; init; } = "MM/dd HH:mm:ss";
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is long time)
|
||||
{
|
||||
var date = DateTimeOffset.FromUnixTimeMilliseconds(time);
|
||||
return date.ToLocalTime().ToString(Format);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ public class FlowerClientItem : BindableObject
|
||||
{
|
||||
static readonly BindableProperty NameProperty = CreateProperty<string, FlowerClientItem>(nameof(Name));
|
||||
static readonly BindableProperty CategoryIdProperty = CreateProperty<int, FlowerClientItem>(nameof(CategoryId));
|
||||
static readonly BindableProperty DaysProperty = CreateProperty<string, FlowerClientItem>(nameof(Days));
|
||||
static readonly BindableProperty CoverProperty = CreateProperty<ImageSource?, FlowerClientItem>(nameof(Cover));
|
||||
static readonly BindableProperty BoundsProperty = CreateProperty<Rect, FlowerClientItem>(nameof(Bounds));
|
||||
|
||||
@ -23,6 +24,11 @@ public class FlowerClientItem : BindableObject
|
||||
get => (int)GetValue(CategoryIdProperty);
|
||||
set => SetValue(CategoryIdProperty, value);
|
||||
}
|
||||
public string Days
|
||||
{
|
||||
get => (string)GetValue(DaysProperty);
|
||||
set => SetValue(DaysProperty, value);
|
||||
}
|
||||
public ImageSource? Cover
|
||||
{
|
||||
get => (ImageSource?)GetValue(CoverProperty);
|
||||
|
124
FlowerApp/Controls/ItemSelectorPage.cs
Normal file
124
FlowerApp/Controls/ItemSelectorPage.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using static Blahblah.FlowerApp.Extensions;
|
||||
|
||||
namespace Blahblah.FlowerApp.Controls;
|
||||
|
||||
class ItemSelectorPage<K, T> : ContentPage where T : IdTextItem<K>
|
||||
{
|
||||
public EventHandler<T>? Selected;
|
||||
|
||||
public ItemSelectorPage(string title, T[] source, bool multiple = false, K[]? selected = null, string display = nameof(IdTextItem<K>.Text), string? detail = null)
|
||||
{
|
||||
Title = title;
|
||||
|
||||
var itemsSource = source.Select(t => new SelectableItem<T>
|
||||
{
|
||||
Item = t,
|
||||
IsSelected = selected != null && selected.Contains(t.Id)
|
||||
}).ToArray();
|
||||
|
||||
var list = new ListView
|
||||
{
|
||||
SelectionMode = ListViewSelectionMode.None,
|
||||
ItemsSource = itemsSource,
|
||||
ItemTemplate = new DataTemplate(() =>
|
||||
{
|
||||
var content = new Grid
|
||||
{
|
||||
Margin = new Thickness(12, 0),
|
||||
ColumnSpacing = 12,
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new(30),
|
||||
new(GridLength.Star),
|
||||
new(GridLength.Auto)
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new SecondaryLabel
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
Text = Res.Check,
|
||||
FontFamily = "FontAwesome"
|
||||
}
|
||||
.Binding(IsVisibleProperty, nameof(SelectableItem<T>.IsSelected)),
|
||||
|
||||
new Label
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(Label.TextProperty, $"{nameof(SelectableItem<T>.Item)}.{display}")
|
||||
.GridColumn(1)
|
||||
}
|
||||
};
|
||||
if (detail != null)
|
||||
{
|
||||
content.Children.Add(
|
||||
new SecondaryLabel
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(Label.TextProperty, $"{nameof(SelectableItem<T>.Item)}.{detail}")
|
||||
.GridColumn(2));
|
||||
}
|
||||
return new ViewCell
|
||||
{
|
||||
View = content
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
list.ItemTapped += List_ItemTapped;
|
||||
|
||||
Content = list;
|
||||
}
|
||||
|
||||
private async void List_ItemTapped(object? sender, ItemTappedEventArgs e)
|
||||
{
|
||||
if (e.Item is SelectableItem<T> item)
|
||||
{
|
||||
Selected?.Invoke(this, item.Item);
|
||||
await Navigation.PopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SelectableItem<T> : BindableObject
|
||||
{
|
||||
public static BindableProperty IsSelectedProperty = CreateProperty<bool, SelectableItem<T>>(nameof(IsSelected));
|
||||
public static BindableProperty ItemProperty = CreateProperty<T, SelectableItem<T>>(nameof(Item));
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => (bool)GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
public T Item
|
||||
{
|
||||
get => (T)GetValue(ItemProperty);
|
||||
set => SetValue(ItemProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
class IdTextItem<T> : BindableObject
|
||||
{
|
||||
public static BindableProperty IdProperty = CreateProperty<T, IdTextItem<T>>(nameof(Id));
|
||||
public static BindableProperty TextProperty = CreateProperty<string, IdTextItem<T>>(nameof(Text));
|
||||
public static BindableProperty DetailProperty = CreateProperty<string?, IdTextItem<T>>(nameof(Detail));
|
||||
|
||||
public T Id
|
||||
{
|
||||
get => (T)GetValue(IdProperty);
|
||||
set => SetValue(IdProperty, value);
|
||||
}
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
public string? Detail
|
||||
{
|
||||
get => (string?)GetValue(DetailProperty);
|
||||
set => SetValue(DetailProperty, value);
|
||||
}
|
||||
}
|
351
FlowerApp/Controls/OptionCell.cs
Normal file
351
FlowerApp/Controls/OptionCell.cs
Normal file
@ -0,0 +1,351 @@
|
||||
using System.ComponentModel;
|
||||
using static Blahblah.FlowerApp.Extensions;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Blahblah.FlowerApp.Controls;
|
||||
|
||||
public class TitleLabel : Label { }
|
||||
|
||||
public class SecondaryLabel : Label { }
|
||||
|
||||
public class IconLabel : Label { }
|
||||
|
||||
public class OptionEntry : Entry { }
|
||||
|
||||
public class OptionEditor : Editor { }
|
||||
|
||||
public class OptionDatePicker : DatePicker { }
|
||||
|
||||
public class OptionTimePicker : TimePicker { }
|
||||
|
||||
public abstract class OptionCell : ViewCell
|
||||
{
|
||||
public static readonly BindableProperty IconProperty = CreateProperty<ImageSource, OptionCell>(nameof(Icon));
|
||||
public static readonly BindableProperty TitleProperty = CreateProperty<string, OptionCell>(nameof(Title));
|
||||
public static readonly BindableProperty IsRequiredProperty = CreateProperty<bool, OptionCell>(nameof(IsRequired));
|
||||
|
||||
[TypeConverter(typeof(ImageSourceConverter))]
|
||||
public ImageSource Icon
|
||||
{
|
||||
get => (ImageSource)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
public string Title
|
||||
{
|
||||
get => (string)GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
public bool IsRequired
|
||||
{
|
||||
get => (bool)GetValue(IsRequiredProperty);
|
||||
set => SetValue(IsRequiredProperty, value);
|
||||
}
|
||||
|
||||
protected abstract View Content { get; }
|
||||
|
||||
public OptionCell()
|
||||
{
|
||||
View = new Grid
|
||||
{
|
||||
BindingContext = this,
|
||||
Padding = new Thickness(20, 0),
|
||||
ColumnSpacing = 12,
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new(GridLength.Auto),
|
||||
new(new GridLength(.35, GridUnitType.Star)),
|
||||
new(new GridLength(.65, GridUnitType.Star))
|
||||
},
|
||||
RowDefinitions = { new(44) },
|
||||
Children =
|
||||
{
|
||||
new Image
|
||||
{
|
||||
WidthRequest = 20,
|
||||
HeightRequest = 20,
|
||||
Aspect = Aspect.AspectFit,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(VisualElement.IsVisibleProperty, nameof(Icon), converter: new VisibleIfNotNullConverter())
|
||||
.Binding(Image.SourceProperty, nameof(Icon)),
|
||||
|
||||
new Grid
|
||||
{
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Star)
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new TitleLabel
|
||||
{
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(Label.TextProperty, nameof(Title)),
|
||||
|
||||
new SecondaryLabel
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Text = "*"
|
||||
}
|
||||
.GridColumn(1)
|
||||
.AppThemeBinding(Label.TextColorProperty, Res.Red100, Res.Red300)
|
||||
.Binding(VisualElement.IsVisibleProperty, nameof(IsRequired)),
|
||||
}
|
||||
}
|
||||
.GridColumn(1),
|
||||
|
||||
Content.GridColumn(2)
|
||||
}
|
||||
}
|
||||
.AppThemeBinding(VisualElement.BackgroundColorProperty, Colors.White, Res.Gray900);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class OptionVerticalCell : OptionCell
|
||||
{
|
||||
public OptionVerticalCell()
|
||||
{
|
||||
View = new Grid
|
||||
{
|
||||
BindingContext = this,
|
||||
Padding = new Thickness(20, 0),
|
||||
ColumnSpacing = 12,
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Star)
|
||||
},
|
||||
RowDefinitions =
|
||||
{
|
||||
new(44),
|
||||
new(GridLength.Star)
|
||||
},
|
||||
Children =
|
||||
{
|
||||
new Image
|
||||
{
|
||||
WidthRequest = 20,
|
||||
HeightRequest = 20,
|
||||
Aspect = Aspect.AspectFit,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(VisualElement.IsVisibleProperty, nameof(Icon), converter: new VisibleIfNotNullConverter())
|
||||
.Binding(Image.SourceProperty, nameof(Icon)),
|
||||
|
||||
new TitleLabel
|
||||
{
|
||||
LineBreakMode = LineBreakMode.TailTruncation,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.GridColumn(1)
|
||||
.Binding(Label.TextProperty, nameof(Title)),
|
||||
|
||||
Content.GridRow(1).GridColumn(1)
|
||||
}
|
||||
}
|
||||
.AppThemeBinding(VisualElement.BackgroundColorProperty, Colors.White, Res.Gray900);
|
||||
}
|
||||
}
|
||||
|
||||
public class OptionTextCell : OptionCell
|
||||
{
|
||||
public static readonly BindableProperty DetailProperty = CreateProperty<string, OptionTextCell>(nameof(Detail));
|
||||
|
||||
public string Detail
|
||||
{
|
||||
get => (string)GetValue(DetailProperty);
|
||||
set => SetValue(DetailProperty, value);
|
||||
}
|
||||
|
||||
protected override View Content => new SecondaryLabel
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(Label.TextProperty, nameof(Detail));
|
||||
}
|
||||
|
||||
public class OptionEntryCell : OptionCell
|
||||
{
|
||||
public static readonly BindableProperty TextProperty = CreateProperty<string, OptionEntryCell>(nameof(Text), defaultBindingMode: BindingMode.TwoWay);
|
||||
public static readonly BindableProperty KeyboardProperty = CreateProperty<Keyboard, OptionEntryCell>(nameof(Keyboard), defaultValue: Keyboard.Default);
|
||||
public static readonly BindableProperty PlaceholderProperty = CreateProperty<string, OptionEntryCell>(nameof(Placeholder));
|
||||
|
||||
public event EventHandler<FocusEventArgs>? Unfocused;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
public Keyboard Keyboard
|
||||
{
|
||||
get => (Keyboard)GetValue(KeyboardProperty);
|
||||
set => SetValue(KeyboardProperty, value);
|
||||
}
|
||||
public string Placeholder
|
||||
{
|
||||
get => (string)GetValue(PlaceholderProperty);
|
||||
set => SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
protected override View Content
|
||||
{
|
||||
get
|
||||
{
|
||||
var entry = new OptionEntry()
|
||||
.Binding(Entry.TextProperty, nameof(Text))
|
||||
.Binding(InputView.KeyboardProperty, nameof(Keyboard))
|
||||
.Binding(Entry.PlaceholderProperty, nameof(Placeholder));
|
||||
entry.Unfocused += Entry_Unfocused;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
private void Entry_Unfocused(object? sender, FocusEventArgs e)
|
||||
{
|
||||
Unfocused?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public class OptionEditorCell : OptionVerticalCell
|
||||
{
|
||||
public static readonly BindableProperty TextProperty = CreateProperty<string, OptionEditorCell>(nameof(Text), defaultBindingMode: BindingMode.TwoWay);
|
||||
public static readonly BindableProperty KeyboardProperty = CreateProperty<Keyboard, OptionEditorCell>(nameof(Keyboard), defaultValue: Keyboard.Default);
|
||||
public static readonly BindableProperty PlaceholderProperty = CreateProperty<string, OptionEditorCell>(nameof(Placeholder));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
public Keyboard Keyboard
|
||||
{
|
||||
get => (Keyboard)GetValue(KeyboardProperty);
|
||||
set => SetValue(KeyboardProperty, value);
|
||||
}
|
||||
public string Placeholder
|
||||
{
|
||||
get => (string)GetValue(PlaceholderProperty);
|
||||
set => SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
protected override View Content => new OptionEditor()
|
||||
.Binding(Editor.TextProperty, nameof(Text))
|
||||
.Binding(InputView.KeyboardProperty, nameof(Keyboard))
|
||||
.Binding(Editor.PlaceholderProperty, nameof(Placeholder));
|
||||
}
|
||||
|
||||
public class OptionSwitchCell : OptionCell
|
||||
{
|
||||
public static readonly BindableProperty IsToggledProperty = CreateProperty<bool, OptionSwitchCell>(nameof(IsToggled), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public bool IsToggled
|
||||
{
|
||||
get => (bool)GetValue(IsToggledProperty);
|
||||
set => SetValue(IsToggledProperty, value);
|
||||
}
|
||||
|
||||
protected override View Content => new Switch
|
||||
{
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
VerticalOptions = LayoutOptions.Center
|
||||
}
|
||||
.Binding(Switch.IsToggledProperty, nameof(IsToggled));
|
||||
}
|
||||
|
||||
public class OptionSelectCell : OptionTextCell
|
||||
{
|
||||
public static readonly BindableProperty CommandProperty = CreateProperty<Command, OptionSelectCell>(nameof(Command));
|
||||
public static readonly BindableProperty CommandParameterProperty = CreateProperty<object, OptionSelectCell>(nameof(CommandParameter));
|
||||
|
||||
public Command Command
|
||||
{
|
||||
get => (Command)GetValue(CommandProperty);
|
||||
set => SetValue(CommandProperty, value);
|
||||
}
|
||||
public object CommandParameter
|
||||
{
|
||||
get => GetValue(CommandParameterProperty);
|
||||
set => SetValue(CommandParameterProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler? DetailTapped;
|
||||
|
||||
protected override View Content
|
||||
{
|
||||
get
|
||||
{
|
||||
var tap = new TapGestureRecognizer();
|
||||
tap.Tapped += OnTapped;
|
||||
|
||||
return new StackLayout
|
||||
{
|
||||
Orientation = StackOrientation.Horizontal,
|
||||
HorizontalOptions = LayoutOptions.End,
|
||||
Children =
|
||||
{
|
||||
new SecondaryLabel
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
}
|
||||
.Binding(Label.TextProperty, nameof(Detail)),
|
||||
|
||||
new IconLabel
|
||||
{
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Margin = new Thickness(6, 0),
|
||||
Text = Res.Right
|
||||
}
|
||||
},
|
||||
GestureRecognizers = { tap }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
DetailTapped?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public class OptionDateTimePickerCell : OptionCell
|
||||
{
|
||||
public static readonly BindableProperty DateProperty = CreateProperty<DateTime, OptionDateTimePickerCell>(nameof(Date), defaultBindingMode: BindingMode.TwoWay);
|
||||
public static readonly BindableProperty TimeProperty = CreateProperty<TimeSpan, OptionDateTimePickerCell>(nameof(Time), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public DateTime Date
|
||||
{
|
||||
get => (DateTime)GetValue(DateProperty);
|
||||
set => SetValue(DateProperty, value);
|
||||
}
|
||||
public TimeSpan Time
|
||||
{
|
||||
get => (TimeSpan)GetValue(TimeProperty);
|
||||
set => SetValue(TimeProperty, value);
|
||||
}
|
||||
|
||||
protected override View Content => new Grid
|
||||
{
|
||||
ColumnDefinitions =
|
||||
{
|
||||
new(GridLength.Star),
|
||||
new(GridLength.Auto),
|
||||
new(GridLength.Auto)
|
||||
},
|
||||
ColumnSpacing = 6,
|
||||
Children =
|
||||
{
|
||||
new OptionDatePicker()
|
||||
.Binding(DatePicker.DateProperty, nameof(Date))
|
||||
.GridColumn(1),
|
||||
|
||||
new OptionTimePicker()
|
||||
.Binding(TimePicker.TimeProperty, nameof(Time))
|
||||
.GridColumn(2)
|
||||
}
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user