@@ -0,0 +1,113 @@
1+namespace Skrepka.iOS
2+
3+open System
4+open System.Threading.Tasks
5+open Foundation
6+open UIKit
7+open AVFoundation
8+open CoreFoundation
9+
10+type private ScanDelegate(tcs: TaskCompletionSource<string option>, session: AVCaptureSession) =
11+ inherit AVCaptureMetadataOutputObjectsDelegate()
12+
13+ let mutable handled = false
14+
15+ override _.DidOutputMetadataObjects(output, metadataObjects, connection) =
16+ if not handled && metadataObjects.Length > 0 then
17+ match metadataObjects.[0] with
18+ | :? AVMetadataMachineReadableCodeObject as code when code.Type = AVMetadataObjectType.QRCode ->
19+ handled <- true
20+ session.StopRunning()
21+
22+ DispatchQueue.MainQueue.DispatchAsync(fun () ->
23+ let window = UIApplication.SharedApplication.KeyWindow
24+
25+ if not (isNull window) && not (isNull window.RootViewController) then
26+ window.RootViewController.DismissViewController(true, fun () ->
27+ tcs.TrySetResult(Some code.StringValue) |> ignore)
28+ else
29+ tcs.TrySetResult(Some code.StringValue) |> ignore)
30+ | _ -> ()
31+
32+module QRScanner =
33+ let mutable private prevent_gc: obj = null
34+
35+ let private presentScanner (tcs: TaskCompletionSource<string option>) =
36+ let session = new AVCaptureSession()
37+
38+ match AVCaptureDevice.GetDefaultDevice(AVMediaTypes.Video) with
39+ | null -> tcs.TrySetResult None |> ignore
40+ | device ->
41+ let mutable error: NSError = null
42+ let input = AVCaptureDeviceInput.FromDevice(device, &error)
43+
44+ if isNull input then
45+ tcs.TrySetResult None |> ignore
46+ else
47+ session.AddInput(input)
48+
49+ let output = new AVCaptureMetadataOutput()
50+ session.AddOutput(output)
51+
52+ let del = new ScanDelegate(tcs, session)
53+ output.SetDelegate(del, DispatchQueue.MainQueue)
54+ output.MetadataObjectTypes <- AVMetadataObjectType.QRCode
55+
56+ let preview = AVCaptureVideoPreviewLayer.FromSession(session)
57+ preview.VideoGravity <- AVLayerVideoGravity.ResizeAspectFill
58+ preview.Frame <- UIScreen.MainScreen.Bounds
59+
60+ let vc = new UIViewController()
61+ vc.View.BackgroundColor <- UIColor.Black
62+ vc.View.Layer.AddSublayer(preview)
63+
64+ let closeBtn = UIButton.FromType(UIButtonType.System)
65+ closeBtn.SetTitle("Cancel", UIControlState.Normal)
66+ closeBtn.SetTitleColor(UIColor.White, UIControlState.Normal)
67+ closeBtn.Frame <- CoreGraphics.CGRect(20., 60., 100., 44.)
68+
69+ closeBtn.TouchUpInside.Add(fun _ ->
70+ session.StopRunning()
71+
72+ vc.DismissViewController(true, fun () ->
73+ tcs.TrySetResult None |> ignore))
74+
75+ vc.View.AddSubview(closeBtn)
76+
77+ prevent_gc <- box (session, del, preview, vc)
78+
79+ tcs.Task.ContinueWith(fun (_: Task<string option>) -> prevent_gc <- null)
80+ |> ignore
81+
82+ session.StartRunning()
83+
84+ let window = UIApplication.SharedApplication.KeyWindow
85+
86+ if not (isNull window) && not (isNull window.RootViewController) then
87+ vc.ModalPresentationStyle <- UIModalPresentationStyle.FullScreen
88+ window.RootViewController.PresentViewController(vc, true, null)
89+ else
90+ session.StopRunning()
91+ tcs.TrySetResult None |> ignore
92+
93+ let scan () : Task<string option> =
94+ let tcs = TaskCompletionSource<string option>()
95+
96+ Avalonia.Threading.Dispatcher.UIThread.Post(fun () ->
97+ let status = AVCaptureDevice.GetAuthorizationStatus(AVAuthorizationMediaType.Video)
98+
99+ match status with
100+ | AVAuthorizationStatus.Denied
101+ | AVAuthorizationStatus.Restricted -> tcs.TrySetResult None |> ignore
102+ | AVAuthorizationStatus.NotDetermined ->
103+ AVCaptureDevice.RequestAccessForMediaType(
104+ AVAuthorizationMediaType.Video,
105+ fun granted ->
106+ if granted then
107+ DispatchQueue.MainQueue.DispatchAsync(fun () -> presentScanner tcs)
108+ else
109+ tcs.TrySetResult None |> ignore
110+ )
111+ | _ -> presentScanner tcs)
112+
113+ tcs.Task