unit Main;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  Winsoft.Android.Camera, FMX.StdCtrls, FMX.Controls.Presentation, FMX.Platform,
  FMX.Colors, Winsoft.FireMonkey.Obr;

type
  TFormMain = class(TForm)
    ToolBar: TToolBar;
    Panel: TPanel;
    ACamera: TACamera;
    ButtonSettings: TButton;
    ButtonStartStop: TButton;
    ButtonTakePicture: TButton;
    LabelBarcode: TLabel;
    ColorBox: TColorBox;
    FObr: TFObr;
    procedure ButtonSettingsClick(Sender: TObject);
    procedure PanelResize(Sender: TObject);
    procedure ButtonTakePictureClick(Sender: TObject);
    procedure ButtonStartStopClick(Sender: TObject);
    procedure ACameraPictureTaken(Sender: TObject; Data: TArray<System.Byte>);
    procedure FormCreate(Sender: TObject);
    procedure ACameraDataReady(Sender: TObject);
    procedure ACameraPermission(Sender: TObject);
    procedure ACameraDisplayRationale(Sender: TObject;
      const APostRationaleProc: TProc);
  private
    { Private declarations }
  public
    { Public declarations }
    [volatile] Paused: Boolean;
    FMXApplicationEventService: IFMXApplicationEventService;
    [volatile] RgbaDataWidth: Integer;
    [volatile] RgbaDataHeight: Integer;
    [volatile] RgbaData: array of Cardinal;
    [volatile] ScanBitmap: TBitmap;
    [volatile] Scanning: Integer;
    procedure Start;
    procedure Stop;
    function HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
  end;

var
  FormMain: TFormMain;

implementation

uses Settings, System.IOUtils, FMX.DialogService, System.SyncObjs;

{$R *.fmx}

{$if CompilerVersion = 31}
{$define Delphi101}
{$endif}

procedure TFormMain.ACameraDataReady(Sender: TObject);
begin
  if TInterlocked.BitTestAndSet(Scanning, 0) then
  begin
    ACamera.ReleaseData; // already scanning so nothing to do with data
    Exit;
  end;

  TThread.CreateAnonymousThread(
    procedure
    var
      Width, Height: Integer;
      Barcode: TObrSymbol;
      Text: string;
    begin
      try
        try
          ACamera.DataToRgba(@RgbaData[0], ACamera.PreviewRotation, ACamera.PreviewHorizMirror);
        finally
          ACamera.ReleaseData;
        end;

        try
          if ACamera.PreviewRotation in [ro0, ro180] then
          begin
            Width := RgbaDataWidth;
            Height := RgbaDataHeight;
          end
          else
          begin
            Width := RgbaDataHeight;
            Height := RgbaDataWidth;
          end;

  {$ifdef Delphi101}
          // to avoid 'Bitmap size too big' bug in Delphi 10.1
          if (ScanBitmap.Width <> Width) or (ScanBitmap.Height <> Height) then
            TThread.Synchronize(nil,
              procedure
              begin
                ScanBitmap.SetSize(Width, Height);
              end);
          else
  {$endif Delphi101}
          begin
            ACamera.UpdateBitmap(ScanBitmap, @RgbaData[0], Width, Height);

            FObr.Picture := ScanBitmap;
            try
              FObr.Scan;
              if FObr.BarcodeCount > 0 then
              begin
                Barcode := FObr.Barcode[0];
                Text := Barcode.SymbologyName + Barcode.SymbologyAddonName + ' ' + Barcode.OrientationName + ' ' + Barcode.DataUtf8;

                // execute UI code in main thread
                TThread.Queue(nil,
                  procedure
                  begin
                    if LabelBarcode.Text <> Text then
                    begin
                      LabelBarcode.Text := Text;
                      ACamera.PlayMediaActionSound(msStartVideoRecording);
                    end;
                  end);
              end
            finally
              FObr.Picture := nil;
            end;
          end
        except
          on E: Exception do
          begin
            Text := E.Message;

            // execute UI code in main thread
            TThread.Queue(nil,
              procedure
              begin
                LabelBarcode.Text := Text;
              end);
          end
        end;
      finally
        TInterlocked.BitTestAndClear(Scanning, 0);
      end;
    end).Start;
end;

procedure TFormMain.ButtonSettingsClick(Sender: TObject);
begin
  if not ACamera.PermissionGranted then
    ACamera.RequestPermission
  else
  begin
    ACamera.Active := True;
    Stop;
    FormSettings.Show;
    ButtonStartStop.Enabled := True;
  end;
end;

procedure TFormMain.Start;
begin
  RgbaDataWidth := ACamera.PreviewSize.Width;
  RgbaDataHeight := ACamera.PreviewSize.Height;
  SetLength(RgbaData, RgbaDataWidth * RgbaDataHeight);

  LabelBarcode.Text := '';
  ACamera.Start;
  ButtonTakePicture.Enabled := True;
  ButtonStartStop.Text := 'Stop';
end;

procedure TFormMain.Stop;
begin
  ACamera.Stop;
  ButtonTakePicture.Enabled := False;
  ButtonStartStop.Text := 'Start';
end;

procedure TFormMain.ButtonStartStopClick(Sender: TObject);
begin
  if ACamera.Capturing then
    Stop
  else
    Start
end;

procedure TFormMain.ButtonTakePictureClick(Sender: TObject);
begin
  ACamera.TakePicture;
end;

procedure TFormMain.ACameraPictureTaken(Sender: TObject; Data: TArray<Byte>);
begin
  TFile.WriteAllBytes(TPath.Combine(TPath.GetPublicPath, 'picture_' + FormatDateTime('yyyymmdd_hhnnss', Now) + '.jpeg'), Data);
end;

procedure TFormMain.PanelResize(Sender: TObject);
begin
  if ACamera.PermissionGranted then
    ACamera.ResizePreview;
end;

procedure TFormMain.FormCreate(Sender: TObject);
begin
  ScanBitmap := TBitmap.Create;

  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, FMXApplicationEventService) then
    FMXApplicationEventService.SetApplicationEventHandler(HandleAppEvent);

  ACamera.LoadMediaActionSound(msStartVideoRecording);
  FObr.Active := True;
end;

procedure TFormMain.ACameraPermission(Sender: TObject);
begin
  if ACamera.PermissionGranted then
    ButtonSettingsClick(Self)
  else
    ShowMessage('Cannot access the camera because the required permission has not been granted');
end;

procedure TFormMain.ACameraDisplayRationale(Sender: TObject; const APostRationaleProc: TProc);
begin
  // Show an explanation to the user *asynchronously* - don't block this thread waiting for the user's response!
  // After the user sees the explanation, invoke the post-rationale routine to request the permissions
  TDialogService.ShowMessage('The application needs to access the camera in order to work',
    procedure(const AResult: TModalResult)
    begin
      APostRationaleProc
    end);
end;

function TFormMain.HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
begin
  if ACamera.PermissionGranted then
    case AAppEvent of
      TApplicationEvent.EnteredBackground:
      begin
        Paused := ACamera.Capturing;
        Stop;
        ACamera.Active := False;
      end;

      TApplicationEvent.WillBecomeForeground:
      begin
        ACamera.Active := True;
        Settings.FormSettings.ApplySettings;
        if Paused then
        begin
          Paused := False;
          Start;
        end;
      end;
    end;

  Result := True;
end;

end.
