Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Image selector anchor - WPB-11605 #2100

Merged
merged 11 commits into from
Nov 25, 2024
69 changes: 38 additions & 31 deletions wire-ios/Wire-iOS/Sources/Managers/Image/ImagePickerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import MobileCoreServices
import UIKit
import UniformTypeIdentifiers
import WireFoundation
import WireSyncEngine

extension UIImage {
Expand All @@ -34,24 +35,26 @@

// MARK: - Properties

private weak var viewController: UIViewController?
private var sourceType: UIImagePickerController.SourceType?
private var completion: ((UIImage) -> Void)?

Check warning on line 38 in wire-ios/Wire-iOS/Sources/Managers/Image/ImagePickerManager.swift

View workflow job for this annotation

GitHub Actions / Test Results

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.
private let mediaShareRestrictionManager = MediaShareRestrictionManager(sessionRestriction: ZMUserSession.shared())
private let device = DeviceWrapper(device: .current)

// MARK: - Methods

func showActionSheet(
on viewController: UIViewController? = UIApplication.shared.topmostViewController(onlyFullScreen: false),
popoverConfiguration: PopoverPresentationControllerConfiguration,
completion: @escaping (UIImage) -> Void
) -> UIAlertController {
self.completion = completion
self.viewController = viewController

return imagePickerAlert()
return imagePickerAlert(viewController: viewController, popoverConfiguration: popoverConfiguration)
}

private func imagePickerAlert() -> UIAlertController {
private func imagePickerAlert(
viewController: UIViewController?,
popoverConfiguration: PopoverPresentationControllerConfiguration
) -> UIAlertController {
typealias Alert = L10n.Localizable.Self.Settings.AccountPictureGroup.Alert
let actionSheet = UIAlertController(
title: Alert.title,
Expand All @@ -62,16 +65,30 @@
// Choose from gallery option, if security flag enabled
if mediaShareRestrictionManager.isPhotoLibraryEnabled {
let galleryAction = UIAlertAction(title: Alert.choosePicture, style: .default) { [weak self] _ in
self?.sourceType = .photoLibrary
self?.getImage(fromSourceType: .photoLibrary)
guard let self, let viewController else { return }

Task { @MainActor in
self.getImage(
fromSourceType: .photoLibrary,
viewController: viewController,
popoverConfiguration: popoverConfiguration
)
}
}
actionSheet.addAction(galleryAction)
}

// Take photo
let cameraAction = UIAlertAction(title: Alert.takePicture, style: .default) { [weak self] _ in
self?.sourceType = .camera
self?.getImage(fromSourceType: .camera)
guard let self, let viewController else { return }

Task { @MainActor in
self.getImage(
fromSourceType: .photoLibrary,
viewController: viewController,
popoverConfiguration: popoverConfiguration
)
}
}
actionSheet.addAction(cameraAction)

Expand All @@ -81,11 +98,13 @@
return actionSheet
}

private func getImage(fromSourceType sourceType: UIImagePickerController.SourceType) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType),
let viewController else {
return
}
@MainActor
private func getImage(
fromSourceType sourceType: UIImagePickerController.SourceType,
viewController: UIViewController,
popoverConfiguration: PopoverPresentationControllerConfiguration
) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType) else { return }

let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
Expand All @@ -101,24 +120,12 @@
imagePickerController.cameraDevice = .front
imagePickerController.modalTransitionStyle = .coverVertical
case .photoLibrary, .savedPhotosAlbum:
if viewController.isIPadRegular() {
if device.userInterfaceIdiom == .pad {
// UIKit will crash if the photo library is not presented using a popoverPresentationController on iPad
// https://developer.apple.com/documentation/uikit/uiimagepickercontroller
imagePickerController.modalPresentationStyle = .popover

if let popoverPresentationController = imagePickerController.popoverPresentationController {
popoverPresentationController.backgroundColor = UIColor.white

// UIKit will crash if the photo library is not presented using a popoverPresentationController
// https://developer.apple.com/documentation/uikit/uiimagepickercontroller
// TODO: [WPB-11605] fix this workaround and choose proper sourceView/sourceRect
popoverPresentationController.sourceView = viewController.view
popoverPresentationController.sourceRect = .init(
origin: .init(
x: viewController.view.safeAreaLayoutGuide.layoutFrame.maxX,
y: viewController.view.safeAreaLayoutGuide.layoutFrame.minY
),
size: .zero
)
}
imagePickerController.popoverPresentationController?.backgroundColor = .white
imagePickerController.configurePopoverPresentationController(using: popoverConfiguration)
}
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@

final class ProfileImagePickerManager: ImagePickerManager {

func selectProfileImage() -> UIAlertController {
showActionSheet { image in
guard let jpegData = image.jpegData else {
return
}
ZMUserSession.shared()?.enqueue {
ZMUserSession.shared()?.userProfileImage.updateImage(imageData: jpegData)
func selectProfileImage(popoverConfiguration: PopoverPresentationControllerConfiguration) -> UIAlertController {
showActionSheet(popoverConfiguration: popoverConfiguration) { image in

Check warning on line 25 in wire-ios/Wire-iOS/Sources/Managers/Image/ProfileImagePickerManager.swift

View workflow job for this annotation

GitHub Actions / Test Results

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.
guard let jpegData = image.jpegData, let session = ZMUserSession.shared() else { return }

session.enqueue {
session.userProfileImage.updateImage(imageData: jpegData)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,12 @@ final class ParticipantsSectionController: GroupDetailsSectionController {
switch configuration {
case let .user(user):
guard let cell = cell as? UserCell else { return unexpectedCellHandler() }
let isE2EICertified = if let userID = user.remoteIdentifier,
let userStatus = viewModel.userStatuses[userID] { userStatus.isE2EICertified } else { false }
let isE2EICertified =
if let userID = user.remoteIdentifier, let userStatus = viewModel.userStatuses[userID] {
userStatus.isE2EICertified
} else {
false
}
cell.configure(
user: user,
isE2EICertified: isE2EICertified,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,12 @@
private func userDidTapProfileImage(_ sender: UIGestureRecognizer) {
guard userRightInterfaceType.selfUserIsPermitted(to: .editProfilePicture) else { return }

let alertController = profileImagePicker.selectProfileImage()
let imageView = profileHeaderViewController.imageView
let alertController = profileImagePicker.selectProfileImage(
popoverConfiguration: .sourceView(sourceView: imageView, sourceRect: .null)
)
if let popoverPresentationController = alertController.popoverPresentationController {
popoverPresentationController.sourceView = profileHeaderViewController.imageView.superview!
popoverPresentationController.sourceRect = profileHeaderViewController.imageView.frame.insetBy(
dx: -4,
dy: -4
)
popoverPresentationController.sourceView = imageView
}
present(alertController, animated: true)
}
Expand All @@ -233,7 +232,7 @@
guard SessionManager.shared?.accountManager.selectedAccount != account else { return }

presentingViewController?.dismiss(animated: true) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {

Check warning on line 235 in wire-ios/Wire-iOS/Sources/UserInterface/SelfProfile/SelfProfileViewController.swift

View workflow job for this annotation

GitHub Actions / Test Results

'mediaPlaybackManager' is deprecated: Will be removed

'mediaPlaybackManager' is deprecated: Will be removed
appDelegate.mediaPlaybackManager?.stop()
}
self.accountSelector?.switchTo(account: account)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import UIKit
import WireSettingsUI
import WireSyncEngine

class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExternalScreenCellDescriptorType {
class SettingsAppearanceCellDescriptor: SettingsGroupCellDescriptorType, SettingsCellDescriptorType {
static let cellType: SettingsTableCellProtocol.Type = SettingsAppearanceCell.self

private var text: String
private let presentationStyle: PresentationStyle

weak var viewController: UIViewController?
let presentationAction: () -> (UIViewController?)
let presentationAction: (_ sender: UIView) -> UIViewController?

var identifier: String?
weak var group: SettingsGroupCellDescriptorType?
Expand All @@ -48,7 +48,7 @@ class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExte
text: String,
previewGenerator: PreviewGeneratorType? = .none,
presentationStyle: PresentationStyle,
presentationAction: @escaping () -> (UIViewController?),
presentationAction: @escaping (_ sender: UIView) -> UIViewController?,
settingsCoordinator: AnySettingsCoordinator
) {
self.text = text
Expand Down Expand Up @@ -81,7 +81,7 @@ class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExte
// MARK: - SettingsCellDescriptorType

func select(_ value: SettingsPropertyValue, sender: UIView) {
guard let controllerToShow = generateViewController() else { return }
guard let controllerToShow = generateViewController(sender: sender) else { return }

switch presentationStyle {
case .alert:
Expand All @@ -97,7 +97,7 @@ class SettingsAppearanceCellDescriptor: SettingsCellDescriptorType, SettingsExte
}
}

func generateViewController() -> UIViewController? {
presentationAction()
private func generateViewController(sender: UIView) -> UIViewController? {
presentationAction(sender)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,6 @@ extension SettingsInternalGroupCellDescriptorType {
}
}

protocol SettingsExternalScreenCellDescriptorType: SettingsGroupCellDescriptorType {
var presentationAction: () -> (UIViewController?) { get }
}

protocol SettingsPropertyCellDescriptorType: SettingsCellDescriptorType {
var settingsProperty: SettingsProperty { get }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,10 @@
return .image(image)
}

let presentationAction: () -> (UIViewController?) = {
profileImagePicker.selectProfileImage()
let presentationAction: (_ sender: UIView) -> UIViewController? = { sender in
profileImagePicker.selectProfileImage(
popoverConfiguration: .sourceView(sourceView: sender, sourceRect: .null)
)
}
return SettingsAppearanceCellDescriptor(
text: L10n.Localizable.Self.Settings.AccountPictureGroup.picture.capitalized,
Expand Down Expand Up @@ -335,9 +337,9 @@
return SettingsCellPreview.color((selfUser.accentColor ?? .default).uiColor)
}

private func colorElementPresentationAction() -> UIViewController {
private func colorElementPresentationAction(sender: UIView) -> UIViewController {
guard
let selfUser = ZMUser.selfUser(),

Check warning on line 342 in wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift

View workflow job for this annotation

GitHub Actions / Test Results

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.
let userSession = ZMUserSession.shared()
else {
assertionFailure("misses prerequisites to present color elements!")
Expand Down Expand Up @@ -421,8 +423,8 @@
)
let actionCancel = UIAlertAction(title: L10n.Localizable.General.cancel, style: .cancel, handler: nil)
alert.addAction(actionCancel)
let actionDelete = UIAlertAction(title: L10n.Localizable.General.ok, style: .destructive) { _ in

Check warning on line 426 in wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift

View workflow job for this annotation

GitHub Actions / Test Results

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.
ZMUserSession.shared()?.enqueue {

Check warning on line 427 in wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift

View workflow job for this annotation

GitHub Actions / Test Results

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.

'shared()' is deprecated: This shared instance has been deprecated. Don't use it.
ZMUserSession.shared()?.initiateUserDeletion()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum AccessoryViewMode: Int {
case alwaysHide
}

class SettingsExternalScreenCellDescriptor: SettingsExternalScreenCellDescriptorType, SettingsControllerGeneratorType {
class SettingsExternalScreenCellDescriptor: SettingsGroupCellDescriptorType, SettingsControllerGeneratorType {
static let cellType: SettingsTableCellProtocol.Type = SettingsTableCell.self
var visible: Bool = true
let title: String
Expand Down
Loading