<?xml version="1.0" encoding="utf-8" ?>
<syncfusion:SfPopup
x:Class="Scan2Cart.Views.PopUps.CartPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:const="clr-namespace:Scan2Cart.Constants"
xmlns:controls="clr-namespace:Scan2Cart.Controls"
xmlns:model="clr-namespace:Scan2Cart.Models"
xmlns:picker="clr-namespace:Syncfusion.Maui.Picker;assembly=Syncfusion.Maui.Picker"
xmlns:syncfusion="clr-namespace:Syncfusion.Maui.Popup;assembly=Syncfusion.Maui.Popup"
xmlns:vm="clr-namespace:Scan2Cart.ViewModels"
Padding="20"
x:DataType="vm:HomePageViewModel"
AnimationMode="Zoom"
IsFullScreen="True"
ShowCloseButton="True"
ShowHeader="False"
StaysOpen="True">
<syncfusion:SfPopup.ContentTemplate>
<DataTemplate x:DataType="vm:HomePageViewModel">
<Grid
Padding="10"
RowDefinitions="40, *, 40"
RowSpacing="5">
<Label FontSize="Large" Text="Your cart" />
<Label
FontAttributes="Bold"
FontFamily="la"
FontSize="30"
HorizontalTextAlignment="End"
Text="{x:Static const:IconFont.WindowClose}"
TextColor="Red">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{x:Binding HideCartCommand}" />
</Label.GestureRecognizers>
</Label>
<CollectionView Grid.Row="1" ItemsSource="{x:Binding Products}">
<CollectionView.ItemsLayout>
<LinearItemsLayout ItemSpacing="10" Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Product">
<Border>
<Grid
Padding="2"
ColumnDefinitions="60,*,70"
ColumnSpacing="5">
<Image
HeightRequest="60"
HorizontalOptions="Start"
Source="{x:Binding ImageUrl}"
WidthRequest="60" />
<VerticalStackLayout Grid.Column="1">
<Label
FontAttributes="Bold"
FontSize="10"
Text="{x:Binding Name}" />
<Label FontSize="10" Text="{x:Binding Description}" />
<Label
Margin="0,10,0,0"
FontAttributes="Bold"
Text="{x:Binding Price}" />
</VerticalStackLayout>
<Button
Grid.Column="2"
BackgroundColor="Transparent"
Command="{Binding OpenQuantityPopUpCommand, Source={x:RelativeSource AncestorType={x:Type vm:HomePageViewModel}}}"
CommandParameter="{x:Binding .}"
FontAttributes="Bold"
FontSize="14"
Text="{x:Binding SelectedQuantity}"
TextColor="{x:AppThemeBinding Dark={x:StaticResource White},
Light={x:StaticResource Black}}" />
</Grid>
<Border.StrokeShape>
<RoundRectangle CornerRadius="8" />
</Border.StrokeShape>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<picker:SfPicker
IsOpen="False"
IsVisible="False"
Mode="Dialog"
TextDisplayMode="FadeAndShrink">
<picker:SfPicker.Columns>
<picker:PickerColumn
HeaderText="Select quantity"
ItemsSource="{x:Binding Product.QuantityRange}"
SelectedItem="{x:Binding Product.SelectedQuantity}" />
</picker:SfPicker.Columns>
</picker:SfPicker>
<Label
Grid.Row="2"
FontSize="30"
HorizontalTextAlignment="Start"
Text="Total" />
<Label
Grid.Row="2"
FontSize="30"
HorizontalTextAlignment="End"
Text="{x:Binding CartTotal}" />
</Grid>
</DataTemplate>
</syncfusion:SfPopup.ContentTemplate>
<syncfusion:SfPopup.PopupStyle>
<syncfusion:PopupStyle
CornerRadius="0"
HasShadow="True"
HeaderBackground="{x:AppThemeBinding Dark='#C8000000',
Light='#00000'}"
MessageBackground="{x:AppThemeBinding Dark='#C8000000',
Light='#C6626161'}" />
</syncfusion:SfPopup.PopupStyle>
</syncfusion:SfPopup>
namespace Scan2Cart.ViewModels;
public partial class HomePageViewModel(IPageService pageService, IDataProvider dataProvider) : BaseViewModel(pageService) {
private bool _isPopupOpening;
private DateTime _lastAlertTime = DateTime.MinValue;
#region ObservableProperties
[ObservableProperty]
public partial bool IsSelectedQtyPopopOpened { get; set; }
[ObservableProperty]
public partial bool IsCartPopopOpen { get; set; }
[ObservableProperty]
public partial bool ShouldDetect { get; set; } = true;
[ObservableProperty]
public partial bool IsProductInfoVisible { get; set; } = false;
[ObservableProperty]
public partial Product? Product { get; set; }
[ObservableProperty]
public partial bool IsPopUpDetectedOpen { get; set; }
[ObservableProperty]
public partial decimal CartTotal { get; set; }
public ObservableCollection<Product> Products { get; set; } = [];
#endregion
private void RecalculateCartTotal() {
CartTotal = Products.Sum(p => p.Total);
}
private void AddProduct(Product p) {
p.PropertyChanged += (s, e) => {
if (e.PropertyName == nameof(Product.Total))
RecalculateCartTotal();
};
Products.Add(p);
RecalculateCartTotal();
}
[RelayCommand]
private async Task BarcodesDetected(IEnumerable<BarcodeResult> results) {
if (_isPopupOpening) return;
var val = results.FirstOrDefault()?.Value;
if (string.IsNullOrEmpty(val)) return;
_isPopupOpening = true;
Product = await dataProvider.GetProductByIdAsync(val);
if (Product is null) {
//await ShowProductNotFoundAlertAsync();
//_isPopupOpening = false;
//return;
AddProduct(new Product {
Id = "DUMMY001",
Name = "Test Product",
Price = 9.99M,
Description = "This is a placeholder product for testing.",
ImageUrl = "https://via.placeholder.com/150",
Quantity = 10,
SelectedQuantity = 1, // initialize
Total = 9.99M // Price × SelectedQuantity
});
AddProduct(new Product {
Id = "TEST001",
Name = "Coffee Beans",
Price = 5.00M,
Description = "Premium roasted coffee beans",
ImageUrl = "https://via.placeholder.com/150",
Quantity = 20,
SelectedQuantity = 1,
Total = 5.00M
});
AddProduct(new Product {
Id = "TEST002",
Name = "Milk Carton",
Price = 2.50M,
Description = "Organic whole milk",
ImageUrl = "https://via.placeholder.com/150",
Quantity = 15,
SelectedQuantity = 1,
Total = 2.50M
});
}
await Task.Delay(100);
MainThread.BeginInvokeOnMainThread(() => {
IsProductInfoVisible = true;
IsPopUpDetectedOpen = true;
});
_isPopupOpening = false;
}
[RelayCommand]
void Yes() {
// Add product if not already in the list
if (!Products.Any(x => x.Id == Product?.Id))
AddProduct(Product!);
// Close popup and reset flags
IsPopUpDetectedOpen = false;
ShouldDetect = true;
}
[RelayCommand]
void No() {
IsPopUpDetectedOpen = false;
ShouldDetect = true;
}
[RelayCommand]
async Task OpenProductPopop() {
ShouldDetect = false;
}
[RelayCommand]
void CloseProductPopop() {
IsProductInfoVisible = false;
IsPopUpDetectedOpen = false;
ShouldDetect = true;
}
[RelayCommand]
async Task ShowCart() {
ShouldDetect = false;
IsCartPopopOpen = true;
}
[RelayCommand]
async Task HideCart() {
IsCartPopopOpen = false;
ShouldDetect = true;
}
[RelayCommand]
void OpenQuantityPopUp(Product p) {
IsSelectedQtyPopopOpened = true;
}
private async Task ShowProductNotFoundAlertAsync() {
var now = DateTime.UtcNow;
if ((now - _lastAlertTime).TotalSeconds < 3) return;
_lastAlertTime = now;
await MainThread.InvokeOnMainThreadAsync(async () => {
await DisplayAlertAsync("Error", "It looks like this item does not exist in your database", "OK");
});
}
}