Skip to content

Commit

Permalink
✨ Mobile | Add user's QR code to scanner page (#1018)
Browse files Browse the repository at this point in the history
* Move to ViewModel

* Implement changes from Betty

* Fix reticle

* Fix camera on new launch if other tab is active
  • Loading branch information
zacharykeeping authored Aug 30, 2024
1 parent bf0b374 commit dee5a26
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 121 deletions.
15 changes: 10 additions & 5 deletions src/MobileUI/Controls/SegmentedControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:SSW.Rewards.Mobile.Controls"
xmlns:converters="clr-namespace:SSW.Rewards.Mobile.Converters"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Name="SegmentedControlParent"
x:Class="SSW.Rewards.Mobile.Controls.SegmentedControl">
<ContentView.Resources>
Expand Down Expand Up @@ -34,11 +35,15 @@
Tapped="Segment_Tapped"
CommandParameter="{Binding .}" />
</Border.GestureRecognizers>
<Label HorizontalOptions="Fill"
VerticalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Text="{Binding Name}" />
<HorizontalStackLayout HorizontalOptions="Center"
VerticalOptions="Center"
Spacing="5">
<Image Source="{Binding Icon}"
IsVisible="{Binding Icon, Converter={toolkit:IsNotNullConverter}}"
HeightRequest="20"/>
<Label VerticalTextAlignment="Center"
Text="{Binding Name}" />
</HorizontalStackLayout>
</Border>
</DataTemplate>
</BindableLayout.ItemTemplate>
Expand Down
2 changes: 2 additions & 0 deletions src/MobileUI/Controls/SegmentedControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public partial class Segment : ObservableObject
{
public string Name { get; set; } = string.Empty;

public ImageSource Icon { get; set; } = null;

public object Value { get; set; }

[ObservableProperty]
Expand Down
2 changes: 1 addition & 1 deletion src/MobileUI/Features/Scanner/QrCodeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public partial class QrCodeViewModel : BaseViewModel

public QrCodeViewModel(IUserService userService)
{
userService.MyQrCodeObservable().Subscribe((myQrCode) =>
userService.MyQrCodeObservable().Subscribe(myQrCode =>
{
QrCode = ImageHelpers.GenerateQrCode(myQrCode);
});
Expand Down
106 changes: 73 additions & 33 deletions src/MobileUI/Features/Scanner/ScanPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:scanner="clr-namespace:BarcodeScanning;assembly=BarcodeScanning.Native.Maui"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:controls="clr-namespace:SSW.Rewards.Mobile.Controls"
xmlns:viewModels="clr-namespace:SSW.Rewards.Mobile.ViewModels"
BackgroundColor="{StaticResource Background}"
x:Class="SSW.Rewards.Mobile.Pages.ScanPage"
x:Name="this">
x:DataType="viewModels:ScanViewModel">
<ContentPage.Resources>
<ResourceDictionary>
<x:Single x:Key="Threshold">1</x:Single>
Expand All @@ -16,16 +18,11 @@
ComparingValue="{StaticResource Threshold}" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<scanner:CameraView x:Name="ScannerView"
OnDetectionFinished="Handle_OnScanResult"
BarcodeSymbologies="QRCode"
ViewfinderMode="True"
TapToFocusEnabled="True"/>

<Button Text="Close"
<Grid RowDefinitions="*,50">
<Button Grid.Row="0"
Text="Close"
ContentLayout="Right, 10"
Command="{Binding Path=DismissCommand, Source={x:Reference this}}"
Command="{Binding Path=DismissCommand}"
Margin="20"
HeightRequest="40"
VerticalOptions="Start"
Expand All @@ -38,21 +35,32 @@
</Button.ImageSource>
</Button>

<Image Source="qr_reticle"/>

<Grid VerticalOptions="End"
RowDefinitions="Auto,*">
<Grid Grid.Row="0"
<Grid Grid.Row="0" IsVisible="{Binding IsScanVisible}">
<scanner:CameraView x:Name="ScannerView"
OnDetectionFinishedCommand="{Binding DetectionFinishedCommand}"
CameraEnabled="{Binding IsCameraEnabled}"
CurrentZoomFactor="{Binding CurrentZoomFactor, Mode=OneWayToSource}"
RequestZoomFactor="{Binding RequestZoomFactor}"
MinZoomFactor="{Binding MinZoomFactor, Mode=OneWayToSource}"
MaxZoomFactor="{Binding MaxZoomFactor, Mode=OneWayToSource}"
BarcodeSymbologies="QRCode"
ViewfinderMode="True"
TapToFocusEnabled="True"/>

<Image Source="qr_reticle" />

<Grid VerticalOptions="End"
x:Name="ZoomButtons"
ColumnDefinitions="Auto,Auto"
HorizontalOptions="Center"
IsVisible="{Binding MaxZoomFactor, Converter={StaticResource GreaterThanOne}, Source={x:Reference ScannerView}, Path=MaxZoomFactor}"
ColumnSpacing="40">
IsVisible="{Binding MaxZoomFactor, Converter={StaticResource GreaterThanOne}}"
ColumnSpacing="20"
Margin="0,0,0,50">
<Button Grid.Column="0"
CornerRadius="30"
WidthRequest="60"
HeightRequest="60"
Command="{Binding Path=ZoomOutCommand, Source={x:Reference this}}">
Command="{Binding Path=ZoomOutCommand}">
<Button.ImageSource>
<FontImageSource Glyph="&#xf8c6;"
FontAutoScalingEnabled="False"
Expand All @@ -64,31 +72,63 @@
CornerRadius="30"
WidthRequest="60"
HeightRequest="60"
Command="{Binding Path=ZoomInCommand, Source={x:Reference this}}">
Command="{Binding Path=ZoomInCommand}">
<Button.ImageSource>
<FontImageSource Glyph="&#xf8c4;"
FontAutoScalingEnabled="False"
FontFamily="FluentIcons"/>
</Button.ImageSource>
</Button>
</Grid>

<Border
Grid.Row="1"
BackgroundColor="{StaticResource Background}"
VerticalOptions="End"
HeightRequest="60"
Margin="20,40"
StrokeShape="RoundRectangle 20">
<Label Text="Scan a QR Code"
</Grid>

<VerticalStackLayout Grid.Row="0"
IsVisible="{Binding IsScanVisible, Converter={toolkit:InvertedBoolConverter}}"
VerticalOptions="Center"
Margin="30"
Spacing="30">
<Grid ColumnDefinitions="40, *"
ColumnSpacing="10"
HorizontalOptions="Center">
<toolkit:AvatarView Grid.Column="0"
VerticalOptions="Center"
HorizontalOptions="Center"
ImageSource="{Binding ProfilePic}"
HeightRequest="40"
WidthRequest="40"
CornerRadius="20"
BorderColor="White"
BorderWidth="2"/>
<Label Grid.Column="1"
VerticalOptions="Center"
VerticalTextAlignment="Center"
Text="{Binding UserName}"
Style="{StaticResource LabelBold}"
FontSize="Large"
FontAutoScalingEnabled="False"
HorizontalTextAlignment="Center"
VerticalOptions="Center"
TextColor="#DDDDDD" />
TextColor="White" />
</Grid>

<Border StrokeThickness="0">
<Border.StrokeShape>
<RoundRectangle CornerRadius="20" />
</Border.StrokeShape>
<Image Aspect="AspectFit" Source="{Binding QrCode}" />
</Border>
</Grid>
</VerticalStackLayout>

<controls:SegmentedControl
Grid.Row="1"
IsVisible="{Binding UserHasQrCode}"
Segments="{Binding Segments}"
VerticalOptions="End"
HeightRequest="50"
SelectedSegment="{Binding SelectedSegment, Mode=OneWayToSource}">
<controls:SegmentedControl.Behaviors>
<toolkit:EventToCommandBehavior
EventName="SelectionChanged"
Command="{Binding FilterBySegmentCommand}" />
</controls:SegmentedControl.Behaviors>
</controls:SegmentedControl>

</Grid>
</ContentPage>
91 changes: 9 additions & 82 deletions src/MobileUI/Features/Scanner/ScanPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,102 +1,29 @@
using BarcodeScanning;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Mopups.Services;
using SSW.Rewards.Mobile.Messages;
namespace SSW.Rewards.Mobile.Pages;

namespace SSW.Rewards.Mobile.Pages;

public partial class ScanPage : IRecipient<EnableScannerMessage>
public partial class ScanPage
{
private readonly ScanResultViewModel _viewModel;
private readonly ScanViewModel _viewModel;
private readonly IFirebaseAnalyticsService _firebaseAnalyticsService;
private const float ZoomFactorStep = 1.0f;

public ScanPage(ScanResultViewModel viewModel, IFirebaseAnalyticsService firebaseAnalyticsService)

public ScanPage(ScanViewModel viewModel, IFirebaseAnalyticsService firebaseAnalyticsService)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.Navigation = Navigation;
_firebaseAnalyticsService = firebaseAnalyticsService;
}

private void Handle_OnScanResult(object sender, OnDetectionFinishedEventArg e)
{
// the handler is called on a thread-pool thread
App.Current.Dispatcher.Dispatch(() =>
{
if (!ScannerView.CameraEnabled || e.BarcodeResults.Length == 0)
{
return;
}

ToggleScanner(false);

var result = e.BarcodeResults.FirstOrDefault()?.RawValue;

var popup = new PopupPages.ScanResult(_viewModel, result);
MopupService.Instance.PushAsync(popup);
});
BindingContext = _viewModel;
}

protected override void OnDisappearing()
{
base.OnDisappearing();

// Reset zoom when exiting camera
if (ScannerView.CurrentZoomFactor > -1)
{
ScannerView.RequestZoomFactor = ScannerView.MinZoomFactor;
}

ToggleScanner(false);
WeakReferenceMessenger.Default.Unregister<EnableScannerMessage>(this);
_viewModel.OnDisappearing();
}

protected override void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
_firebaseAnalyticsService.Log("ScanPage");
WeakReferenceMessenger.Default.Register(this);

ToggleScanner(true);
}


public void Receive(EnableScannerMessage message)
{
ToggleScanner(true);
}

private void ToggleScanner(bool toggleOn)
{
ScannerView.CameraEnabled = toggleOn;
}

[RelayCommand]
private async Task Dismiss()
{
if (Navigation.ModalStack.Count > 0)
{
await Navigation.PopModalAsync();
}
}

[RelayCommand]
private void ZoomIn()
{
// CurrentZoomFactor can default to -1, so we start at the MinZoomFactor in this case
var currentZoom = Math.Max(ScannerView.CurrentZoomFactor, ScannerView.MinZoomFactor);
var maxZoom = ScannerView.MaxZoomFactor;

ScannerView.RequestZoomFactor = Math.Min(currentZoom + ZoomFactorStep, maxZoom);
}

[RelayCommand]
private void ZoomOut()
{
var currentZoom = ScannerView.CurrentZoomFactor;
var minZoom = ScannerView.MinZoomFactor;

ScannerView.RequestZoomFactor = Math.Max(currentZoom - ZoomFactorStep, minZoom);
}
}
Loading

0 comments on commit dee5a26

Please sign in to comment.