Compare commits

..

No commits in common. "50bfaf7236faef49ab07e4fa6aefe02794a36d7f" and "f825760fe93d6db6ef6de2a670996a8a9075b23c" have entirely different histories.

7 changed files with 38 additions and 81 deletions

View File

@ -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
} }

View File

@ -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
} }
} }

View File

@ -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 }
}

View File

@ -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)

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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 {