Skip to content

Commit

Permalink
Mock the entire of the iOS camera API.
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Jan 17, 2024
1 parent 7633a1c commit c891f0c
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 155 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ jobs:
# only occur in CI, and can't be reproduced locally. When it runs, it will
# open an SSH server (URL reported in the logs) so you can ssh into the CI
# machine.
# - uses: actions/checkout@v3
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3
# if: failure()
2 changes: 2 additions & 0 deletions cocoa/tests_backend/hardware/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@


class CameraProbe(AppProbe):
allow_no_camera = True

def __init__(self, monkeypatch, app_probe):
super().__init__(app_probe.app)

Expand Down
21 changes: 0 additions & 21 deletions core/src/toga/hardware/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,24 +121,3 @@ def take_photo(
photo = PhotoResult(None)
self._impl.take_photo(photo, device=device, flash=flash)
return photo

# async def record_video(
# self,
# device: CameraDevice | None = None,
# flash: FlashMode = FlashMode.AUTO,
# quality: VideoQuality = VideoQuality.MEDIUM,
# ) -> toga.Video:
# """Capture a video using one of the device's cameras.
#
# If the platform requires permission to access the camera and/or
# microphone, and the user hasn't previously provided that permission,
# this will cause permission to be requested.
#
# :param device: The camera device to use. If a specific device is *not*
# specified, a default camera will be used.
# :param flash: The flash mode to use; defaults to "auto"
# :returns: The :any:`toga.Video` captured by the camera.
# """
# future = asyncio.get_event_loop().create_future()
# self._impl.record_video(future, device=device, flash=flash)
# return future
78 changes: 36 additions & 42 deletions iOS/src/toga_iOS/hardware/camera.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from rubicon.objc import Block, objc_method
import warnings

from rubicon.objc import Block, NSObject, objc_method

import toga
from toga.constants import FlashMode

# for classes that need to be monkeypatched for testing
from toga_iOS import libs as iOS
from toga_iOS.libs import (
AVAuthorizationStatus,
AVCaptureDevice,
AVMediaTypeVideo,
UIImagePickerController,
NSBundle,
UIImagePickerControllerCameraCaptureMode,
UIImagePickerControllerCameraDevice,
UIImagePickerControllerCameraFlashMode,
Expand All @@ -27,7 +31,7 @@ def name(self):
return self._name

def has_flash(self):
return UIImagePickerController.isFlashAvailableForCameraDevice(self.native)
return iOS.UIImagePickerController.isFlashAvailableForCameraDevice(self.native)


def native_flash_mode(flash):
Expand All @@ -44,7 +48,7 @@ def native_flash_mode(flash):
# }.get(quality, UIImagePickerControllerQualityType.Medium)


class TogaImagePickerController(UIImagePickerController):
class TogaImagePickerDelegate(NSObject):
@objc_method
def imagePickerController_didFinishPickingMediaWithInfo_(
self, picker, info
Expand All @@ -57,17 +61,29 @@ def imagePickerController_didFinishPickingMediaWithInfo_(
@objc_method
def imagePickerControllerDidCancel_(self, picker) -> None:
picker.dismissViewControllerAnimated(True, completion=None)

self.result.set_result(None)


class Camera:
def __init__(self, interface):
self.interface = interface

self.native = TogaImagePickerController.alloc().init()
self.native.sourceType = UIImagePickerControllerSourceTypeCamera
self.native.delegate = self.native
if NSBundle.mainBundle.objectForInfoDictionaryKey("NSCameraUsageDescription"):
if iOS.UIImagePickerController.isSourceTypeAvailable(
UIImagePickerControllerSourceTypeCamera
):
self.native = iOS.UIImagePickerController.new()
self.native.sourceType = UIImagePickerControllerSourceTypeCamera
self.native.delegate = TogaImagePickerDelegate.new()
else:
self.native = None
else: # pragma: no cover
# The app doesn't have the NSCameraUsageDescription key (e.g., via
# `permission.camera` in Briefcase). No-cover because we can't manufacture
# this condition in testing.
raise RuntimeError(
"Application metadata does not declare that the app will use the camera."
)

def has_permission(self, allow_unknown=False):
if allow_unknown:
Expand All @@ -79,18 +95,18 @@ def has_permission(self, allow_unknown=False):
valid_values = {AVAuthorizationStatus.Authorized.value}

return (
AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
iOS.AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
in valid_values
)

def request_permission(self, future):
# This block is invoked when the permission is granted; however, permission is
# granted from a different (inaccessible) thread, so it isn't picked up by
# coverage.
def permission_complete(result) -> None: # pragma: no cover
def permission_complete(result) -> None:
future.set_result(result)

AVCaptureDevice.requestAccessForMediaType(
iOS.AVCaptureDevice.requestAccessForMediaType(
AVMediaTypeVideo, completionHandler=Block(permission_complete, None, bool)
)

Expand All @@ -103,7 +119,7 @@ def get_devices(self):
native=UIImagePickerControllerCameraDevice.Rear,
)
]
if UIImagePickerController.isCameraDeviceAvailable(
if iOS.UIImagePickerController.isCameraDeviceAvailable(
UIImagePickerControllerCameraDevice.Rear
)
else []
Expand All @@ -115,14 +131,17 @@ def get_devices(self):
native=UIImagePickerControllerCameraDevice.Front,
)
]
if UIImagePickerController.isCameraDeviceAvailable(
if iOS.UIImagePickerController.isCameraDeviceAvailable(
UIImagePickerControllerCameraDevice.Front
)
else []
)

def take_photo(self, result, device, flash):
if self.has_permission(allow_unknown=True):
if self.native is None:
warnings.warn("No camera is available")
result.set_result(None)
elif self.has_permission(allow_unknown=True):
# Configure the controller to take a photo
self.native.cameraCaptureMode = (
UIImagePickerControllerCameraCaptureMode.Photo
Expand All @@ -136,37 +155,12 @@ def take_photo(self, result, device, flash):
)
self.native.cameraFlashMode = native_flash_mode(flash)

# Attach the result to the picker
self.native.result = result
# Attach the result to the delegate
self.native.delegate.result = result

# Show the pane
toga.App.app.current_window._impl.native.rootViewController.presentViewController(
self.native, animated=True, completion=None
)
else:
raise PermissionError("App does not have permission to take photos")

# def record_video(self, result, device, flash):
# if self.has_video_permission(allow_unknown=True):
# # Configure the controller to take a photo
# self.native.cameraCaptureMode = (
# UIImagePickerControllerCameraCaptureMode.Video
# )

# self.native.showsCameraControls = True
# self.native.cameraDevice = (
# device._impl.native
# if device
# else UIImagePickerControllerCameraDevice.Rear
# )
# self.native.cameraFlashMode = native_flash_mode(flash)

# # Attach the result to the picker
# self.native.result = result

# # Show the pane
# toga.App.app.current_window._impl.native.rootViewController.presentViewController(
# self.native, animated=True, completion=None
# )
# else:
# raise PermissionError("App does not have permission to take photos")
Loading

0 comments on commit c891f0c

Please sign in to comment.