Compare commits
No commits in common. "50bfaf7236faef49ab07e4fa6aefe02794a36d7f" and "f825760fe93d6db6ef6de2a670996a8a9075b23c" have entirely different histories.
50bfaf7236
...
f825760fe9
|
@ -136,7 +136,6 @@ extension DraftAttachment {
|
||||||
|
|
||||||
//private let attachmentTypeIdentifier = "space.vaccor.Tusker.composition-attachment"
|
//private let attachmentTypeIdentifier = "space.vaccor.Tusker.composition-attachment"
|
||||||
|
|
||||||
private let imageType = UTType.image.identifier
|
|
||||||
private let jpegType = UTType.jpeg.identifier
|
private let jpegType = UTType.jpeg.identifier
|
||||||
private let pngType = UTType.png.identifier
|
private let pngType = UTType.png.identifier
|
||||||
private let mp4Type = UTType.mpeg4Movie.identifier
|
private let mp4Type = UTType.mpeg4Movie.identifier
|
||||||
|
@ -148,26 +147,14 @@ extension DraftAttachment: NSItemProviderReading {
|
||||||
// todo: is there a better way of handling movies than manually adding all possible UTI types?
|
// todo: is there a better way of handling movies than manually adding all possible UTI types?
|
||||||
// just using kUTTypeMovie doesn't work, because we need the actually type in order to get the file extension
|
// just using kUTTypeMovie doesn't work, because we need the actually type in order to get the file extension
|
||||||
// without the file extension, getting the thumbnail and exporting the video for attachment upload fails
|
// without the file extension, getting the thumbnail and exporting the video for attachment upload fails
|
||||||
[/*typeIdentifier, */ gifType, jpegType, pngType, imageType, mp4Type, quickTimeType]
|
[/*typeIdentifier, */gifType, jpegType, pngType, mp4Type, quickTimeType]
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> DraftAttachment {
|
public static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> DraftAttachment {
|
||||||
var data = data
|
|
||||||
var type = UTType(typeIdentifier)!
|
|
||||||
|
|
||||||
// this seems to only occur when the item is a UIImage, rather than just image data,
|
|
||||||
// which seems to only occur when sharing a screenshot directly from the markup screen
|
|
||||||
if type == .image,
|
|
||||||
let image = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIImage.self, from: data),
|
|
||||||
let pngData = image.pngData() {
|
|
||||||
data = pngData
|
|
||||||
type = .png
|
|
||||||
}
|
|
||||||
|
|
||||||
let attachment = DraftAttachment(entity: DraftsPersistentContainer.shared.persistentStoreCoordinator.managedObjectModel.entitiesByName["DraftAttachment"]!, insertInto: nil)
|
let attachment = DraftAttachment(entity: DraftsPersistentContainer.shared.persistentStoreCoordinator.managedObjectModel.entitiesByName["DraftAttachment"]!, insertInto: nil)
|
||||||
attachment.id = UUID()
|
attachment.id = UUID()
|
||||||
attachment.fileURL = try writeDataToFile(data, id: attachment.id, type: type)
|
attachment.fileURL = try writeDataToFile(data, id: attachment.id, type: UTType(typeIdentifier)!)
|
||||||
attachment.fileType = type.identifier
|
attachment.fileType = typeIdentifier
|
||||||
attachment.attachmentDescription = ""
|
attachment.attachmentDescription = ""
|
||||||
return attachment
|
return attachment
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,7 @@ class ToggleFollowHashtagService {
|
||||||
self.presenter = presenter
|
self.presenter = presenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
func toggleFollow() async {
|
||||||
func toggleFollow() async -> Bool {
|
|
||||||
let success: Bool
|
|
||||||
let context = mastodonController.persistentContainer.viewContext
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
var config: ToastConfiguration
|
var config: ToastConfiguration
|
||||||
if let existing = mastodonController.followedHashtags.first(where: { $0.name == hashtagName }) {
|
if let existing = mastodonController.followedHashtags.first(where: { $0.name == hashtagName }) {
|
||||||
|
@ -38,14 +36,11 @@ class ToggleFollowHashtagService {
|
||||||
config = ToastConfiguration(title: "Unfollowed Hashtag")
|
config = ToastConfiguration(title: "Unfollowed Hashtag")
|
||||||
config.systemImageName = "checkmark"
|
config.systemImageName = "checkmark"
|
||||||
config.dismissAutomaticallyAfter = 2
|
config.dismissAutomaticallyAfter = 2
|
||||||
|
|
||||||
success = true
|
|
||||||
} catch {
|
} catch {
|
||||||
config = ToastConfiguration(from: error, with: "Error Unfollowing Hashtag", in: presenter) { toast in
|
config = ToastConfiguration(from: error, with: "Error Unfollowing Hashtag", in: presenter) { toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
await self.toggleFollow()
|
await self.toggleFollow()
|
||||||
}
|
}
|
||||||
success = false
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
|
@ -58,19 +53,15 @@ class ToggleFollowHashtagService {
|
||||||
config = ToastConfiguration(title: "Followed Hashtag")
|
config = ToastConfiguration(title: "Followed Hashtag")
|
||||||
config.systemImageName = "checkmark"
|
config.systemImageName = "checkmark"
|
||||||
config.dismissAutomaticallyAfter = 2
|
config.dismissAutomaticallyAfter = 2
|
||||||
|
|
||||||
success = true
|
|
||||||
} catch {
|
} catch {
|
||||||
config = ToastConfiguration(from: error, with: "Error Following Hashtag", in: presenter) { toast in
|
config = ToastConfiguration(from: error, with: "Error Following Hashtag", in: presenter) { toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
await self.toggleFollow()
|
await self.toggleFollow()
|
||||||
}
|
}
|
||||||
success = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
presenter.showToast(configuration: config, animated: true)
|
presenter.showToast(configuration: config, animated: true)
|
||||||
mastodonController.persistentContainer.save(context: context)
|
mastodonController.persistentContainer.save(context: context)
|
||||||
return success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,6 +288,15 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate, Collect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeSavedHashtag(_ hashtag: Hashtag) {
|
||||||
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
|
let req = SavedHashtag.fetchRequest(name: hashtag.name, account: mastodonController.accountInfo!)
|
||||||
|
if let hashtag = try? context.fetch(req).first {
|
||||||
|
context.delete(hashtag)
|
||||||
|
try! context.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func removeSavedInstance(_ instanceURL: URL) {
|
func removeSavedInstance(_ instanceURL: URL) {
|
||||||
let context = mastodonController.persistentContainer.viewContext
|
let context = mastodonController.persistentContainer.viewContext
|
||||||
let req = SavedInstance.fetchRequest(url: instanceURL, account: mastodonController.accountInfo!)
|
let req = SavedInstance.fetchRequest(url: instanceURL, account: mastodonController.accountInfo!)
|
||||||
|
@ -298,45 +307,36 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate, Collect
|
||||||
}
|
}
|
||||||
|
|
||||||
private func trailingSwipeActionsForCell(at indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
private func trailingSwipeActionsForCell(at indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
var actions = [UIContextualAction]()
|
let title: String
|
||||||
|
let handler: UIContextualAction.Handler
|
||||||
switch dataSource.itemIdentifier(for: indexPath) {
|
switch dataSource.itemIdentifier(for: indexPath) {
|
||||||
case let .list(list):
|
case let .list(list):
|
||||||
actions.append(UIContextualAction(style: .destructive, title: "Delete", handler: { _, _, completion in
|
title = NSLocalizedString("Delete", comment: "delete swipe action title")
|
||||||
|
handler = { (_, _, completion) in
|
||||||
self.deleteList(list, completion: completion)
|
self.deleteList(list, completion: completion)
|
||||||
}))
|
}
|
||||||
|
|
||||||
case let .savedHashtag(hashtag):
|
case let .savedHashtag(hashtag):
|
||||||
let name = hashtag.name.lowercased()
|
title = NSLocalizedString("Unsave", comment: "unsave swipe action title")
|
||||||
let context = mastodonController.persistentContainer.viewContext
|
handler = { (_, _, completion) in
|
||||||
let existing = try? context.fetch(SavedHashtag.fetchRequest(name: name, account: mastodonController.accountInfo!)).first
|
self.removeSavedHashtag(hashtag)
|
||||||
if let existing {
|
completion(true)
|
||||||
actions.append(UIContextualAction(style: .destructive, title: "Unsave", handler: { _, _, completion in
|
|
||||||
context.delete(existing)
|
|
||||||
try! context.save()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
if mastodonController.instanceFeatures.canFollowHashtags,
|
|
||||||
mastodonController.followedHashtags.contains(where: { $0.name.lowercased() == name }) {
|
|
||||||
actions.append(UIContextualAction(style: .destructive, title: "Unfollow", handler: { _, _, completion in
|
|
||||||
Task {
|
|
||||||
let success =
|
|
||||||
await ToggleFollowHashtagService(hashtagName: hashtag.name, presenter: self)
|
|
||||||
.toggleFollow()
|
|
||||||
completion(success)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case let .savedInstance(url):
|
case let .savedInstance(url):
|
||||||
actions.append(UIContextualAction(style: .destructive, title: "Unsave", handler: { _, _, completion in
|
title = NSLocalizedString("Unsave", comment: "unsave swipe action title")
|
||||||
|
handler = { (_, _, completion) in
|
||||||
self.removeSavedInstance(url)
|
self.removeSavedInstance(url)
|
||||||
completion(true)
|
completion(true)
|
||||||
}))
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return UISwipeActionsConfiguration(actions: actions)
|
|
||||||
|
return UISwipeActionsConfiguration(actions: [
|
||||||
|
UIContextualAction(style: .destructive, title: title, handler: handler)
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Collection View Delegate
|
// MARK: - Collection View Delegate
|
||||||
|
@ -581,7 +581,3 @@ extension ExploreViewController: UICollectionViewDragDelegate {
|
||||||
return [UIDragItem(itemProvider: provider)]
|
return [UIDragItem(itemProvider: provider)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExploreViewController: TuskerNavigationDelegate {
|
|
||||||
var apiController: MastodonController! { mastodonController }
|
|
||||||
}
|
|
||||||
|
|
|
@ -101,8 +101,7 @@ class AttachmentsContainerView: UIView {
|
||||||
accessibilityElements.append(attachmentView)
|
accessibilityElements.append(attachmentView)
|
||||||
if Preferences.shared.showUncroppedMediaInline,
|
if Preferences.shared.showUncroppedMediaInline,
|
||||||
let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
|
let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
|
||||||
// clamp to prevent excessively short/tall attachments
|
aspectRatio = attachmentAspectRatio
|
||||||
aspectRatio = max(min(attachmentAspectRatio, 2/1), 1/2)
|
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
let left = createAttachmentView(index: 0, hSize: .half, vSize: .full)
|
let left = createAttachmentView(index: 0, hSize: .half, vSize: .full)
|
||||||
|
|
|
@ -94,12 +94,6 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
|
|
||||||
// MARK: - HTML Parsing
|
// MARK: - HTML Parsing
|
||||||
func setTextFromHtml(_ html: String) {
|
func setTextFromHtml(_ html: String) {
|
||||||
// this shouldn't be necessary, but sometimes when the text view is updated before
|
|
||||||
// being added to the view hierarchy, it doesn't get tintColorDidChange calld
|
|
||||||
// when it's actually added, so links have the wrong color
|
|
||||||
// see #402
|
|
||||||
self.tintColor = Preferences.shared.accentColor.color
|
|
||||||
|
|
||||||
self.attributedText = htmlConverter.convert(html)
|
self.attributedText = htmlConverter.convert(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,23 +141,13 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
$0.isPointerInteractionEnabled = true
|
$0.isPointerInteractionEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// using a UIStackView for this does not layout correctly on the first pass
|
private lazy var actionsCountHStack = UIStackView(arrangedSubviews: [
|
||||||
// (everything is shifted slightly to the right for some reason)
|
reblogsCountButton,
|
||||||
// so do it manually, since there are only two subvviews
|
favoritesCountButton,
|
||||||
private lazy var actionsCountHStack = UIView().configure {
|
]).configure {
|
||||||
reblogsCountButton.translatesAutoresizingMaskIntoConstraints = false
|
$0.axis = .horizontal
|
||||||
$0.addSubview(reblogsCountButton)
|
$0.spacing = 8
|
||||||
favoritesCountButton.translatesAutoresizingMaskIntoConstraints = false
|
$0.distribution = .fillProportionally
|
||||||
$0.addSubview(favoritesCountButton)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
reblogsCountButton.leadingAnchor.constraint(equalTo: $0.leadingAnchor),
|
|
||||||
reblogsCountButton.topAnchor.constraint(equalTo: $0.topAnchor),
|
|
||||||
reblogsCountButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
|
||||||
favoritesCountButton.leadingAnchor.constraint(equalTo: reblogsCountButton.trailingAnchor, constant: 8),
|
|
||||||
favoritesCountButton.topAnchor.constraint(equalTo: $0.topAnchor),
|
|
||||||
favoritesCountButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
|
||||||
favoritesCountButton.trailingAnchor.constraint(equalTo: $0.trailingAnchor),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private let timestampAndClientLabel = UILabel().configure {
|
private let timestampAndClientLabel = UILabel().configure {
|
||||||
|
|
|
@ -125,7 +125,7 @@ private func captureError(_ error: Client.Error, in mastodonController: Mastodon
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let code = event.tags!["response_code"],
|
if let code = event.tags!["response_code"],
|
||||||
code == "401" || code == "403" || code == "404" || code == "502" || code == "503" {
|
code == "401" || code == "403" || code == "404" || code == "502" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch mastodonController.instanceFeatures.instanceType {
|
switch mastodonController.instanceFeatures.instanceType {
|
||||||
|
|
Loading…
Reference in New Issue