Compare commits

..

4 Commits

Author SHA1 Message Date
Shadowfacts 130da9d4cc Improve status collapse animation
Use an additional label with no content and no height to absorb the
extra space creating during collapse when the content text view
disappears immediately.
2021-06-12 11:39:15 -04:00
Shadowfacts 472b9aa5e2 Fixes for large image animations on devices with square screns 2021-06-12 11:26:44 -04:00
Shadowfacts 3413dff8f9 Present compose screen in new window on iOS 15 and iPad/Mac 2021-06-11 10:50:31 -04:00
Shadowfacts 66e8fce488 Fix crash when conversation VC tries to restore from unloaded status 2021-06-11 10:19:59 -04:00
12 changed files with 129 additions and 35 deletions

View File

@ -17,21 +17,39 @@ class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate {
return return
} }
guard LocalData.shared.onboardingComplete else {
UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil)
return
}
let account: LocalData.UserAccountInfo let account: LocalData.UserAccountInfo
let controller: MastodonController
let draft: Draft? let draft: Draft?
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity, if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
let activityAccount = UserActivityManager.getAccount(from: activity) { if let activityAccount = UserActivityManager.getAccount(from: activity) {
account = activityAccount account = activityAccount
draft = UserActivityManager.getDraft(from: activity) } else {
// todo: this potentially changes the account for the draft, should show the same warning to user as in the drafts selection screen
account = LocalData.shared.getMostRecentAccount()!
}
controller = MastodonController.getForAccount(account)
if let activityDraft = UserActivityManager.getDraft(from: activity) {
draft = activityDraft
} else if let mentioning = activity.userInfo?["mentioning"] as? String {
draft = controller.createDraft(inReplyToID: nil, mentioningAcct: mentioning)
} else {
draft = nil
}
} else { } else {
account = LocalData.shared.getMostRecentAccount()! account = LocalData.shared.getMostRecentAccount()!
controller = MastodonController.getForAccount(account)
draft = nil draft = nil
} }
let controller = MastodonController.getForAccount(account)
session.mastodonController = controller session.mastodonController = controller
controller.getOwnAccount() controller.getOwnAccount()
controller.getOwnInstance() controller.getOwnInstance()

View File

@ -51,8 +51,14 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
} }
var dismissInteractionController: LargeImageInteractionController? var dismissInteractionController: LargeImageInteractionController?
var isInteractivelyAnimatingDismissal: Bool = false {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool { override var prefersStatusBarHidden: Bool {
return true return !isInteractivelyAnimatingDismissal
} }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none return .none

View File

@ -132,6 +132,30 @@ class ConversationTableViewController: EnhancedTableViewController {
visibilityBarButtonItem = UIBarButtonItem(image: initialImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed)) visibilityBarButtonItem = UIBarButtonItem(image: initialImage, style: .plain, target: self, action: #selector(toggleVisibilityButtonPressed))
navigationItem.rightBarButtonItem = visibilityBarButtonItem navigationItem.rightBarButtonItem = visibilityBarButtonItem
loadMainStatus()
}
private func loadMainStatus() {
if let mainStatus = mastodonController.persistentContainer.status(for: mainStatusID) {
self.mainStatusLoaded(mainStatus)
} else {
let request = Client.getStatus(id: mainStatusID)
mastodonController.run(request) { (response) in
switch response {
case let .success(status, _):
let viewContext = self.mastodonController.persistentContainer.viewContext
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false, context: viewContext) { (statusMO) in
self.mainStatusLoaded(statusMO)
}
case .failure(_):
fatalError()
}
}
}
}
private func mainStatusLoaded(_ mainStatus: StatusMO) {
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState) let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState)
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
@ -139,12 +163,10 @@ class ConversationTableViewController: EnhancedTableViewController {
snapshot.appendItems([mainStatusItem], toSection: .statuses) snapshot.appendItems([mainStatusItem], toSection: .statuses)
dataSource.apply(snapshot, animatingDifferences: false) dataSource.apply(snapshot, animatingDifferences: false)
guard let mainStatus = self.mastodonController.persistentContainer.status(for: self.mainStatusID) else {
fatalError("Missing cached status \(self.mainStatusID)")
}
let mainStatusInReplyToID = mainStatus.inReplyToID let mainStatusInReplyToID = mainStatus.inReplyToID
mainStatus.incrementReferenceCount() mainStatus.incrementReferenceCount()
// todo: it would be nice to cache these contexts
let request = Status.getContext(mainStatusID) let request = Status.getContext(mainStatusID)
mastodonController.run(request) { response in mastodonController.run(request) { response in
guard case let .success(context, _) = response else { fatalError() } guard case let .success(context, _) = response else { fatalError() }

View File

@ -51,8 +51,14 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
private var prevZoomScale: CGFloat? private var prevZoomScale: CGFloat?
private var isGrayscale = false private var isGrayscale = false
var isInteractivelyAnimatingDismissal: Bool = false {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool { override var prefersStatusBarHidden: Bool {
return true return !isInteractivelyAnimatingDismissal
} }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none return .none

View File

@ -43,8 +43,14 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
var animationGifData: Data? { largeImageVC?.animationGifData } var animationGifData: Data? { largeImageVC?.animationGifData }
var dismissInteractionController: LargeImageInteractionController? var dismissInteractionController: LargeImageInteractionController?
var isInteractivelyAnimatingDismissal: Bool = false {
didSet {
setNeedsStatusBarAppearanceUpdate()
}
}
override var prefersStatusBarHidden: Bool { override var prefersStatusBarHidden: Bool {
return true return !isInteractivelyAnimatingDismissal
} }
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .none return .none

View File

@ -15,6 +15,7 @@ protocol LargeImageAnimatableViewController: UIViewController {
var animationImage: UIImage? { get } var animationImage: UIImage? { get }
var animationGifData: Data? { get } var animationGifData: Data? { get }
var dismissInteractionController: LargeImageInteractionController? { get } var dismissInteractionController: LargeImageInteractionController? { get }
var isInteractivelyAnimatingDismissal: Bool { get set }
} }
extension LargeImageAnimatableViewController { extension LargeImageAnimatableViewController {
@ -74,7 +75,7 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
toVC.largeImageController?.contentView.isHidden = true toVC.largeImageController?.contentView.isHidden = true
toVC.largeImageController?.setControlsVisible(false, animated: false) toVC.largeImageController?.setControlsVisible(false, animated: false)
var finalFrameSize = finalVCFrame.inset(by: fromVC.view.safeAreaInsets).size var finalFrameSize = finalVCFrame.inset(by: toVC.view.safeAreaInsets).size
let newWidth = finalFrameSize.width / image.size.width let newWidth = finalFrameSize.width / image.size.width
let newHeight = finalFrameSize.height / image.size.height let newHeight = finalFrameSize.height / image.size.height
if newHeight < newWidth { if newHeight < newWidth {

View File

@ -14,9 +14,9 @@ class LargeImageInteractionController: UIPercentDrivenInteractiveTransition {
var direction: CGFloat? var direction: CGFloat?
var shouldCompleteTransition = false var shouldCompleteTransition = false
private weak var viewController: UIViewController! private weak var viewController: LargeImageAnimatableViewController!
init(viewController: UIViewController) { init(viewController: LargeImageAnimatableViewController) {
super.init() super.init()
self.viewController = viewController self.viewController = viewController
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
@ -42,6 +42,7 @@ class LargeImageInteractionController: UIPercentDrivenInteractiveTransition {
viewController.dismiss(animated: true) viewController.dismiss(animated: true)
case .changed: case .changed:
shouldCompleteTransition = progress > 0.5 || velocity > 1000 shouldCompleteTransition = progress > 0.5 || velocity > 1000
viewController.isInteractivelyAnimatingDismissal = progress > 0.1
update(progress) update(progress)
case .cancelled: case .cancelled:
inProgress = false inProgress = false
@ -59,4 +60,9 @@ class LargeImageInteractionController: UIPercentDrivenInteractiveTransition {
} }
} }
override func cancel() {
super.cancel()
viewController.isInteractivelyAnimatingDismissal = false
}
} }

View File

@ -353,10 +353,17 @@ fileprivate extension MainSidebarViewController.Item {
extension MainSplitViewController: TuskerRootViewController { extension MainSplitViewController: TuskerRootViewController {
@objc func presentCompose() { @objc func presentCompose() {
let vc = ComposeHostingController(mastodonController: mastodonController) if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
let nav = EnhancedNavigationViewController(rootViewController: vc) let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
nav.presentationController?.delegate = vc let options = UIWindowScene.ActivationRequestOptions()
present(nav, animated: true) options.preferredPresentationStyle = .prominent
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: compose, options: options, errorHandler: nil)
} else {
let vc = ComposeHostingController(mastodonController: mastodonController)
let nav = EnhancedNavigationViewController(rootViewController: vc)
nav.presentationController?.delegate = vc
present(nav, animated: true)
}
} }
func select(tab: MainTabBarViewController.Tab) { func select(tab: MainTabBarViewController.Tab) {

View File

@ -212,10 +212,17 @@ extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
extension MainTabBarViewController: TuskerRootViewController { extension MainTabBarViewController: TuskerRootViewController {
@objc func presentCompose() { @objc func presentCompose() {
let vc = ComposeHostingController(mastodonController: mastodonController) if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
let nav = EnhancedNavigationViewController(rootViewController: vc) let compose = UserActivityManager.newPostActivity(mentioning: nil, accountID: mastodonController.accountInfo!.id)
nav.presentationController?.delegate = vc let options = UIWindowScene.ActivationRequestOptions()
present(nav, animated: true) options.preferredPresentationStyle = .prominent
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: compose, options: options, errorHandler: nil)
} else {
let vc = ComposeHostingController(mastodonController: mastodonController)
let nav = EnhancedNavigationViewController(rootViewController: vc)
nav.presentationController?.delegate = vc
present(nav, animated: true)
}
} }
func select(tab: Tab) { func select(tab: Tab) {

View File

@ -70,6 +70,7 @@ class UserActivityManager {
// TODO: check not currently showing compose screen // TODO: check not currently showing compose screen
let mentioning = activity.userInfo?["mentioning"] as? String let mentioning = activity.userInfo?["mentioning"] as? String
let draft = mastodonController.createDraft(mentioningAcct: mentioning) let draft = mastodonController.createDraft(mentioningAcct: mentioning)
// todo: this shouldn't use self.mastodonController, it should get the right one based on the userInfo accountID
let composeVC = ComposeHostingController(draft: draft, mastodonController: mastodonController) let composeVC = ComposeHostingController(draft: draft, mastodonController: mastodonController)
present(UINavigationController(rootViewController: composeVC)) present(UINavigationController(rootViewController: composeVC))
} }

View File

@ -89,15 +89,22 @@ extension TuskerNavigationDelegate {
} }
func compose(editing draft: Draft) { func compose(editing draft: Draft) {
let compose = ComposeHostingController(draft: draft, mastodonController: apiController) if #available(iOS 15.0, *), UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
let vc = UINavigationController(rootViewController: compose) let options = UIWindowScene.ActivationRequestOptions()
vc.presentationController?.delegate = compose options.preferredPresentationStyle = .prominent
present(vc, animated: true) UIApplication.shared.requestSceneSessionActivation(nil, userActivity: compose, options: options, errorHandler: nil)
} else {
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
let nav = UINavigationController(rootViewController: compose)
nav.presentationController?.delegate = compose
present(nav, animated: true)
}
} }
func compose(inReplyToID: String? = nil, mentioningAcct: String? = nil) { func compose(inReplyToID: String? = nil, mentioningAcct: String? = nil) {
let draft = apiController.createDraft(inReplyToID: inReplyToID, mentioningAcct: mentioningAcct) let draft = apiController.createDraft(inReplyToID: inReplyToID, mentioningAcct: mentioningAcct)
DraftsManager.shared.add(draft)
compose(editing: draft) compose(editing: draft)
} }

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19115.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19107.4"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -38,7 +39,7 @@
</constraints> </constraints>
</imageView> </imageView>
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="gIY-Wp-RSk"> <stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="gIY-Wp-RSk">
<rect key="frame" x="58" y="0.0" width="277" height="169.5"/> <rect key="frame" x="58" y="0.0" width="277" height="173.5"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="3Sm-P0-ySf"> <stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="3Sm-P0-ySf">
<rect key="frame" x="0.0" y="0.0" width="277" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="277" height="20.5"/>
@ -132,13 +133,19 @@
<rect key="frame" x="0.0" y="169.5" width="277" height="0.0"/> <rect key="frame" x="0.0" y="169.5" width="277" height="0.0"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="1" verticalCompressionResistancePriority="1" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oFl-rC-EEN">
<rect key="frame" x="0.0" y="173.5" width="277" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="oie-wK-IpU"> <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="oie-wK-IpU">
<rect key="frame" x="0.0" y="54" width="50" height="22"/> <rect key="frame" x="0.0" y="54" width="50" height="22"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bubble.left.and.bubble.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="KdQ-Zn-IhD"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="bubble.left.and.bubble.right" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="KdQ-Zn-IhD">
<rect key="frame" x="0.0" y="1" width="25.5" height="21.5"/> <rect key="frame" x="0.0" y="0.0" width="25.5" height="21.5"/>
<color key="tintColor" systemColor="secondaryLabelColor"/> <color key="tintColor" systemColor="secondaryLabelColor"/>
<accessibility key="accessibilityConfiguration" label="Is a reply"/> <accessibility key="accessibilityConfiguration" label="Is a reply"/>
<constraints> <constraints>
@ -218,7 +225,7 @@
<constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="0Tm-v7-Ts4"/> <constraint firstItem="gIY-Wp-RSk" firstAttribute="leading" secondItem="QMP-j2-HLn" secondAttribute="trailing" constant="8" id="0Tm-v7-Ts4"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="8" id="2Ao-Gj-fY3"/> <constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="8" id="2Ao-Gj-fY3"/>
<constraint firstItem="TUP-Nz-5Yh" firstAttribute="trailing" secondItem="ve3-Y1-NQH" secondAttribute="trailingMargin" id="3l0-tE-Ak1"/> <constraint firstItem="TUP-Nz-5Yh" firstAttribute="trailing" secondItem="ve3-Y1-NQH" secondAttribute="trailingMargin" id="3l0-tE-Ak1"/>
<constraint firstItem="TUP-Nz-5Yh" firstAttribute="top" secondItem="gIY-Wp-RSk" secondAttribute="bottom" id="4KL-a3-qyf"/> <constraint firstItem="TUP-Nz-5Yh" firstAttribute="top" secondItem="gIY-Wp-RSk" secondAttribute="bottom" constant="-4" id="4KL-a3-qyf"/>
<constraint firstItem="oie-wK-IpU" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="4" id="7Mp-WS-FhY"/> <constraint firstItem="oie-wK-IpU" firstAttribute="top" secondItem="QMP-j2-HLn" secondAttribute="bottom" constant="4" id="7Mp-WS-FhY"/>
<constraint firstItem="TUP-Nz-5Yh" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="oie-wK-IpU" secondAttribute="bottom" id="7Xp-Sa-Rfk"/> <constraint firstItem="TUP-Nz-5Yh" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="oie-wK-IpU" secondAttribute="bottom" id="7Xp-Sa-Rfk"/>
<constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/> <constraint firstItem="QMP-j2-HLn" firstAttribute="top" secondItem="ve3-Y1-NQH" secondAttribute="top" id="PC4-Bi-QXm"/>
@ -276,7 +283,7 @@
<image name="ellipsis" catalog="system" width="128" height="37"/> <image name="ellipsis" catalog="system" width="128" height="37"/>
<image name="globe" catalog="system" width="128" height="121"/> <image name="globe" catalog="system" width="128" height="121"/>
<image name="pin.fill" catalog="system" width="119" height="128"/> <image name="pin.fill" catalog="system" width="119" height="128"/>
<image name="repeat" catalog="system" width="128" height="99"/> <image name="repeat" catalog="system" width="128" height="98"/>
<image name="star.fill" catalog="system" width="128" height="116"/> <image name="star.fill" catalog="system" width="128" height="116"/>
<systemColor name="labelColor"> <systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>