Compare commits
No commits in common. "fe32356bceb14ebe2b4013e1b1dd2c87b835494c" and "8fc915d6a0534cee63881013f5d2a7d8cf567f91" have entirely different histories.
fe32356bce
...
8fc915d6a0
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,19 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2023.4 (70)
|
|
||||||
Features/Improvements:
|
|
||||||
- Add GIF/ALT badges to attachments
|
|
||||||
- Add menu action to hide/show reblogs from specific accounts
|
|
||||||
- Apply Mastodon's link truncation
|
|
||||||
- Add preference to hide link preview cards
|
|
||||||
- Tweak link preview card border color in dark mode
|
|
||||||
- Unify haptic feedback across the app
|
|
||||||
- Move Drafts button to the nav bar when the post doesn't have any content, to reduce accidental presses
|
|
||||||
|
|
||||||
Bugfixes:
|
|
||||||
- Fix status URLs with fragments not being resolved
|
|
||||||
- Workaround for local-only posts not being decodable when logged in to Akkoma instances
|
|
||||||
|
|
||||||
## 2023.3 (69)
|
## 2023.3 (69)
|
||||||
Features/Improvements:
|
Features/Improvements:
|
||||||
- Add Tip Jar under Preferences
|
- Add Tip Jar under Preferences
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# Haptic Feedback
|
|
||||||
|
|
||||||
## Selection changed
|
|
||||||
`UISelectionFeedbackGenerator`
|
|
||||||
|
|
||||||
## Actions
|
|
||||||
On success, `UIImpactFeedbackGenerator` with the `.light` style. On error, `UINotificationFeedbackGenerator` with the `.error` type.
|
|
|
@ -62,7 +62,7 @@ public class DuckableContainerViewController: UIViewController, DuckableViewCont
|
||||||
guard case .idle = state else {
|
guard case .idle = state else {
|
||||||
if animated,
|
if animated,
|
||||||
case .ducked(_, placeholder: let placeholder) = state {
|
case .ducked(_, placeholder: let placeholder) = state {
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
let origConstant = placeholder.topConstraint.constant
|
let origConstant = placeholder.topConstraint.constant
|
||||||
UIView.animateKeyframes(withDuration: 0.4, delay: 0) {
|
UIView.animateKeyframes(withDuration: 0.4, delay: 0) {
|
||||||
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
|
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
|
||||||
|
|
|
@ -109,12 +109,6 @@ public final class Account: AccountProtocol, Decodable {
|
||||||
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/follow")
|
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/follow")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func setShowReblogs(_ accountID: String, showReblogs: Bool) -> Request<Relationship> {
|
|
||||||
return Request(method: .post, path: "/api/v1/accounts/\(accountID)/follow", body: ParametersBody([
|
|
||||||
"reblogs" => showReblogs
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func unfollow(_ accountID: String) -> Request<Relationship> {
|
public static func unfollow(_ accountID: String) -> Request<Relationship> {
|
||||||
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow")
|
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow")
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,47 +44,6 @@ public final class Status: StatusProtocol, Decodable {
|
||||||
|
|
||||||
public var applicationName: String? { application?.name }
|
public var applicationName: String? { application?.name }
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
self.id = try container.decode(String.self, forKey: .id)
|
|
||||||
self.uri = try container.decode(String.self, forKey: .uri)
|
|
||||||
self.url = try container.decodeIfPresent(WebURL.self, forKey: .url)
|
|
||||||
self.account = try container.decode(Account.self, forKey: .account)
|
|
||||||
self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID)
|
|
||||||
self.inReplyToAccountID = try container.decodeIfPresent(String.self, forKey: .inReplyToAccountID)
|
|
||||||
self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog)
|
|
||||||
self.content = try container.decode(String.self, forKey: .content)
|
|
||||||
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
|
||||||
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
|
|
||||||
self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount)
|
|
||||||
self.favouritesCount = try container.decode(Int.self, forKey: .favouritesCount)
|
|
||||||
self.reblogged = try container.decodeIfPresent(Bool.self, forKey: .reblogged)
|
|
||||||
self.favourited = try container.decodeIfPresent(Bool.self, forKey: .favourited)
|
|
||||||
self.muted = try container.decodeIfPresent(Bool.self, forKey: .muted)
|
|
||||||
self.sensitive = try container.decode(Bool.self, forKey: .sensitive)
|
|
||||||
self.spoilerText = try container.decode(String.self, forKey: .spoilerText)
|
|
||||||
if let visibility = try? container.decode(Status.Visibility.self, forKey: .visibility) {
|
|
||||||
self.visibility = visibility
|
|
||||||
self.localOnly = try container.decodeIfPresent(Bool.self, forKey: .localOnly)
|
|
||||||
} else if let s = try? container.decode(String.self, forKey: .visibility),
|
|
||||||
s == "local" {
|
|
||||||
// hacky workaround for #332, akkoma describes local posts with a separate visibility
|
|
||||||
self.visibility = .public
|
|
||||||
self.localOnly = true
|
|
||||||
} else {
|
|
||||||
throw DecodingError.dataCorruptedError(forKey: .visibility, in: container, debugDescription: "Could not decode visibility")
|
|
||||||
}
|
|
||||||
self.attachments = try container.decode([Attachment].self, forKey: .attachments)
|
|
||||||
self.mentions = try container.decode([Mention].self, forKey: .mentions)
|
|
||||||
self.hashtags = try container.decode([Hashtag].self, forKey: .hashtags)
|
|
||||||
self.application = try container.decodeIfPresent(Application.self, forKey: .application)
|
|
||||||
self.language = try container.decodeIfPresent(String.self, forKey: .language)
|
|
||||||
self.pinned = try container.decodeIfPresent(Bool.self, forKey: .pinned)
|
|
||||||
self.bookmarked = try container.decodeIfPresent(Bool.self, forKey: .bookmarked)
|
|
||||||
self.card = try container.decodeIfPresent(Card.self, forKey: .card)
|
|
||||||
self.poll = try container.decodeIfPresent(Poll.self, forKey: .poll)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getContext(_ statusID: String) -> Request<ConversationContext> {
|
public static func getContext(_ statusID: String) -> Request<ConversationContext> {
|
||||||
return Request<ConversationContext>(method: .get, path: "/api/v1/statuses/\(statusID)/context")
|
return Request<ConversationContext>(method: .get, path: "/api/v1/statuses/\(statusID)/context")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2372,7 +2372,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 70;
|
CURRENT_PROJECT_VERSION = 69;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
@ -2380,7 +2380,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2023.4;
|
MARKETING_VERSION = 2023.3;
|
||||||
OTHER_CODE_SIGN_FLAGS = "";
|
OTHER_CODE_SIGN_FLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -2437,7 +2437,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 70;
|
CURRENT_PROJECT_VERSION = 69;
|
||||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||||
|
@ -2446,7 +2446,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2023.4;
|
MARKETING_VERSION = 2023.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -2588,7 +2588,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 70;
|
CURRENT_PROJECT_VERSION = 69;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
@ -2596,7 +2596,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2023.4;
|
MARKETING_VERSION = 2023.3;
|
||||||
OTHER_CODE_SIGN_FLAGS = "";
|
OTHER_CODE_SIGN_FLAGS = "";
|
||||||
OTHER_LDFLAGS = "";
|
OTHER_LDFLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||||
|
@ -2616,7 +2616,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 70;
|
CURRENT_PROJECT_VERSION = 69;
|
||||||
INFOPLIST_FILE = Tusker/Info.plist;
|
INFOPLIST_FILE = Tusker/Info.plist;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
@ -2624,7 +2624,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2023.4;
|
MARKETING_VERSION = 2023.3;
|
||||||
OTHER_CODE_SIGN_FLAGS = "";
|
OTHER_CODE_SIGN_FLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -2721,7 +2721,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 70;
|
CURRENT_PROJECT_VERSION = 69;
|
||||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||||
|
@ -2730,7 +2730,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2023.4;
|
MARKETING_VERSION = 2023.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -2747,7 +2747,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 70;
|
CURRENT_PROJECT_VERSION = 69;
|
||||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||||
|
@ -2756,7 +2756,7 @@
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 2023.4;
|
MARKETING_VERSION = 2023.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
|
|
@ -55,21 +55,7 @@ struct HTMLConverter {
|
||||||
case let node as Element:
|
case let node as Element:
|
||||||
let attributed = NSMutableAttributedString(string: "", attributes: [.font: font, .foregroundColor: color])
|
let attributed = NSMutableAttributedString(string: "", attributes: [.font: font, .foregroundColor: color])
|
||||||
for child in node.getChildNodes() {
|
for child in node.getChildNodes() {
|
||||||
var appendEllipsis = false
|
|
||||||
if node.tagName() == "a",
|
|
||||||
let el = child as? Element {
|
|
||||||
if el.hasClass("invisible") {
|
|
||||||
continue
|
|
||||||
} else if el.hasClass("ellipsis") {
|
|
||||||
appendEllipsis = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attributed.append(attributedTextForHTMLNode(child, usePreformattedText: usePreformattedText || node.tagName() == "pre"))
|
attributed.append(attributedTextForHTMLNode(child, usePreformattedText: usePreformattedText || node.tagName() == "pre"))
|
||||||
|
|
||||||
if appendEllipsis {
|
|
||||||
attributed.append(NSAttributedString("…"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch node.tagName() {
|
switch node.tagName() {
|
||||||
|
|
|
@ -44,7 +44,6 @@ class Preferences: Codable, ObservableObject {
|
||||||
self.showIsStatusReplyIcon = try container.decode(Bool.self, forKey: .showIsStatusReplyIcon)
|
self.showIsStatusReplyIcon = try container.decode(Bool.self, forKey: .showIsStatusReplyIcon)
|
||||||
self.alwaysShowStatusVisibilityIcon = try container.decode(Bool.self, forKey: .alwaysShowStatusVisibilityIcon)
|
self.alwaysShowStatusVisibilityIcon = try container.decode(Bool.self, forKey: .alwaysShowStatusVisibilityIcon)
|
||||||
self.hideActionsInTimeline = try container.decodeIfPresent(Bool.self, forKey: .hideActionsInTimeline) ?? false
|
self.hideActionsInTimeline = try container.decodeIfPresent(Bool.self, forKey: .hideActionsInTimeline) ?? false
|
||||||
self.showLinkPreviews = try container.decodeIfPresent(Bool.self, forKey: .showLinkPreviews) ?? true
|
|
||||||
self.leadingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .leadingStatusSwipeActions) ?? leadingStatusSwipeActions
|
self.leadingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .leadingStatusSwipeActions) ?? leadingStatusSwipeActions
|
||||||
self.trailingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .trailingStatusSwipeActions) ?? trailingStatusSwipeActions
|
self.trailingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .trailingStatusSwipeActions) ?? trailingStatusSwipeActions
|
||||||
|
|
||||||
|
@ -98,7 +97,6 @@ class Preferences: Codable, ObservableObject {
|
||||||
try container.encode(showIsStatusReplyIcon, forKey: .showIsStatusReplyIcon)
|
try container.encode(showIsStatusReplyIcon, forKey: .showIsStatusReplyIcon)
|
||||||
try container.encode(alwaysShowStatusVisibilityIcon, forKey: .alwaysShowStatusVisibilityIcon)
|
try container.encode(alwaysShowStatusVisibilityIcon, forKey: .alwaysShowStatusVisibilityIcon)
|
||||||
try container.encode(hideActionsInTimeline, forKey: .hideActionsInTimeline)
|
try container.encode(hideActionsInTimeline, forKey: .hideActionsInTimeline)
|
||||||
try container.encode(showLinkPreviews, forKey: .showLinkPreviews)
|
|
||||||
try container.encode(leadingStatusSwipeActions, forKey: .leadingStatusSwipeActions)
|
try container.encode(leadingStatusSwipeActions, forKey: .leadingStatusSwipeActions)
|
||||||
try container.encode(trailingStatusSwipeActions, forKey: .trailingStatusSwipeActions)
|
try container.encode(trailingStatusSwipeActions, forKey: .trailingStatusSwipeActions)
|
||||||
|
|
||||||
|
@ -146,7 +144,6 @@ class Preferences: Codable, ObservableObject {
|
||||||
@Published var showIsStatusReplyIcon = false
|
@Published var showIsStatusReplyIcon = false
|
||||||
@Published var alwaysShowStatusVisibilityIcon = false
|
@Published var alwaysShowStatusVisibilityIcon = false
|
||||||
@Published var hideActionsInTimeline = false
|
@Published var hideActionsInTimeline = false
|
||||||
@Published var showLinkPreviews = true
|
|
||||||
@Published var leadingStatusSwipeActions: [StatusSwipeAction] = [.favorite, .reblog]
|
@Published var leadingStatusSwipeActions: [StatusSwipeAction] = [.favorite, .reblog]
|
||||||
@Published var trailingStatusSwipeActions: [StatusSwipeAction] = [.reply, .share]
|
@Published var trailingStatusSwipeActions: [StatusSwipeAction] = [.reply, .share]
|
||||||
|
|
||||||
|
@ -208,7 +205,6 @@ class Preferences: Codable, ObservableObject {
|
||||||
case showIsStatusReplyIcon
|
case showIsStatusReplyIcon
|
||||||
case alwaysShowStatusVisibilityIcon
|
case alwaysShowStatusVisibilityIcon
|
||||||
case hideActionsInTimeline
|
case hideActionsInTimeline
|
||||||
case showLinkPreviews
|
|
||||||
case leadingStatusSwipeActions
|
case leadingStatusSwipeActions
|
||||||
case trailingStatusSwipeActions
|
case trailingStatusSwipeActions
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,6 @@ struct ComposeToolbar: View {
|
||||||
.font(.system(size: imageSize))
|
.font(.system(size: imageSize))
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.hoverEffect()
|
.hoverEffect()
|
||||||
.transition(.opacity.animation(.linear(duration: 0.2)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentInput = uiState.currentInput,
|
if let currentInput = uiState.currentInput,
|
||||||
|
@ -75,11 +74,16 @@ struct ComposeToolbar: View {
|
||||||
.accessibilityLabel(format.accessibilityLabel)
|
.accessibilityLabel(format.accessibilityLabel)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.hoverEffect()
|
.hoverEffect()
|
||||||
.transition(.opacity.animation(.linear(duration: 0.2)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
Button(action: self.draftsButtonPressed) {
|
||||||
|
Text("Drafts")
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.hoverEffect()
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.frame(minWidth: minWidth)
|
.frame(minWidth: minWidth)
|
||||||
|
@ -115,6 +119,10 @@ struct ComposeToolbar: View {
|
||||||
uiState.currentInput?.beginAutocompletingEmoji()
|
uiState.currentInput?.beginAutocompletingEmoji()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func draftsButtonPressed() {
|
||||||
|
uiState.isShowingDraftsList = true
|
||||||
|
}
|
||||||
|
|
||||||
private func formatAction(_ format: StatusFormat) -> () -> Void {
|
private func formatAction(_ format: StatusFormat) -> () -> Void {
|
||||||
{
|
{
|
||||||
uiState.currentInput?.applyFormat(format)
|
uiState.currentInput?.applyFormat(format)
|
||||||
|
|
|
@ -239,9 +239,7 @@ struct ComposeView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var postButton: some View {
|
private var postButton: some View {
|
||||||
if draft.hasContent {
|
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
await self.postStatus()
|
await self.postStatus()
|
||||||
|
@ -251,13 +249,6 @@ struct ComposeView: View {
|
||||||
}
|
}
|
||||||
.keyboardShortcut(.return, modifiers: .command)
|
.keyboardShortcut(.return, modifiers: .command)
|
||||||
.disabled(!postButtonEnabled)
|
.disabled(!postButtonEnabled)
|
||||||
} else {
|
|
||||||
Button {
|
|
||||||
uiState.isShowingDraftsList = true
|
|
||||||
} label: {
|
|
||||||
Text("Drafts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cancel() {
|
private func cancel() {
|
||||||
|
|
|
@ -98,7 +98,6 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
||||||
if context.coordinator.skipSettingTextOnNextUpdate {
|
if context.coordinator.skipSettingTextOnNextUpdate {
|
||||||
context.coordinator.skipSettingTextOnNextUpdate = false
|
context.coordinator.skipSettingTextOnNextUpdate = false
|
||||||
} else {
|
} else {
|
||||||
context.coordinator.skipNextAutocompleteUpdate = true
|
|
||||||
uiView.text = text
|
uiView.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +185,6 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
||||||
var caretScrollPositionAnimator: UIViewPropertyAnimator?
|
var caretScrollPositionAnimator: UIViewPropertyAnimator?
|
||||||
|
|
||||||
var skipSettingTextOnNextUpdate = false
|
var skipSettingTextOnNextUpdate = false
|
||||||
var skipNextAutocompleteUpdate = false
|
|
||||||
|
|
||||||
var toolbarElements: [ComposeUIState.ToolbarElement] {
|
var toolbarElements: [ComposeUIState.ToolbarElement] {
|
||||||
[.emojiPicker, .formattingButtons]
|
[.emojiPicker, .formattingButtons]
|
||||||
|
@ -326,10 +324,6 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateAutocompleteState() {
|
private func updateAutocompleteState() {
|
||||||
guard !skipNextAutocompleteUpdate else {
|
|
||||||
skipNextAutocompleteUpdate = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let textView = textView,
|
guard let textView = textView,
|
||||||
let text = textView.text,
|
let text = textView.text,
|
||||||
let (lastWordStartIndex, foundFirstAtSign) = findAutocompleteLastWord() else {
|
let (lastWordStartIndex, foundFirstAtSign) = findAutocompleteLastWord() else {
|
||||||
|
|
|
@ -189,11 +189,11 @@ class ConversationViewController: UIViewController {
|
||||||
indicator.startAnimating()
|
indicator.startAnimating()
|
||||||
state = .loading(indicator)
|
state = .loading(indicator)
|
||||||
|
|
||||||
let url = WebURL(url)!.serialized(excludingFragment: true)
|
let url = WebURL(url)!
|
||||||
let request = Client.search(query: url, types: [.statuses], resolve: true)
|
let request = Client.search(query: url.serialized(), types: [.statuses], resolve: true)
|
||||||
do {
|
do {
|
||||||
let (results, _) = try await mastodonController.run(request)
|
let (results, _) = try await mastodonController.run(request)
|
||||||
guard let status = results.statuses.first(where: { $0.url?.serialized() == url }) else {
|
guard let status = results.statuses.first(where: { $0.url == url }) else {
|
||||||
throw UnableToResolveError()
|
throw UnableToResolveError()
|
||||||
}
|
}
|
||||||
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
private(set) var accountViews: [FastSwitchingAccountView] = []
|
private(set) var accountViews: [FastSwitchingAccountView] = []
|
||||||
private var lastSelectedAccountViewIndex: Int?
|
private var lastSelectedAccountViewIndex: Int?
|
||||||
private var selectionChangedFeedbackGenerator: UISelectionFeedbackGenerator?
|
private var selectionChangedFeedbackGenerator: UIImpactFeedbackGenerator?
|
||||||
private var touchBeganFeedbackWorkItem: DispatchWorkItem?
|
private var touchBeganFeedbackWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
var itemOrientation: ItemOrientation = .iconsTrailing
|
var itemOrientation: ItemOrientation = .iconsTrailing
|
||||||
|
@ -148,7 +148,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
private func switchAccount(newIndex: Int, hapticFeedback: Bool = true) {
|
private func switchAccount(newIndex: Int, hapticFeedback: Bool = true) {
|
||||||
if newIndex == 0 { // add account placeholder
|
if newIndex == 0 { // add account placeholder
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
selectionChangedFeedbackGenerator?.impactOccurred()
|
||||||
}
|
}
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
if account.id != LocalData.shared.mostRecentAccountID {
|
if account.id != LocalData.shared.mostRecentAccountID {
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
selectionChangedFeedbackGenerator?.impactOccurred()
|
||||||
}
|
}
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
|
|
||||||
|
@ -178,8 +178,8 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
@objc private func handleLongPress(_ recognizer: UIGestureRecognizer) {
|
@objc private func handleLongPress(_ recognizer: UIGestureRecognizer) {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
selectionChangedFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
|
||||||
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
|
selectionChangedFeedbackGenerator?.impactOccurred()
|
||||||
selectionChangedFeedbackGenerator?.prepare()
|
selectionChangedFeedbackGenerator?.prepare()
|
||||||
|
|
||||||
show()
|
show()
|
||||||
|
@ -231,7 +231,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
lastSelectedAccountViewIndex = selectedAccountViewIndex
|
lastSelectedAccountViewIndex = selectedAccountViewIndex
|
||||||
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
selectionChangedFeedbackGenerator?.impactOccurred(intensity: 0.5)
|
||||||
selectionChangedFeedbackGenerator?.prepare()
|
selectionChangedFeedbackGenerator?.prepare()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,7 +260,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
// if the user is merely tapping, not initiating a pan, we don't want to trigger a double-impact
|
// if the user is merely tapping, not initiating a pan, we don't want to trigger a double-impact
|
||||||
// if the tap ends very quickly, this will be cancelled
|
// if the tap ends very quickly, this will be cancelled
|
||||||
touchBeganFeedbackWorkItem = DispatchWorkItem {
|
touchBeganFeedbackWorkItem = DispatchWorkItem {
|
||||||
self.selectionChangedFeedbackGenerator?.selectionChanged()
|
self.selectionChangedFeedbackGenerator?.impactOccurred(intensity: 0.5)
|
||||||
self.selectionChangedFeedbackGenerator?.prepare()
|
self.selectionChangedFeedbackGenerator?.prepare()
|
||||||
self.touchBeganFeedbackWorkItem = nil
|
self.touchBeganFeedbackWorkItem = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,9 +84,6 @@ struct AppearancePrefsView : View {
|
||||||
Toggle(isOn: $preferences.hideActionsInTimeline) {
|
Toggle(isOn: $preferences.hideActionsInTimeline) {
|
||||||
Text("Hide Actions on Timeline")
|
Text("Hide Actions on Timeline")
|
||||||
}
|
}
|
||||||
Toggle(isOn: $preferences.showLinkPreviews) {
|
|
||||||
Text("Show Link Previews")
|
|
||||||
}
|
|
||||||
NavigationLink("Leading Swipe Actions") {
|
NavigationLink("Leading Swipe Actions") {
|
||||||
SwipeActionsPrefsView(selection: $preferences.leadingStatusSwipeActions)
|
SwipeActionsPrefsView(selection: $preferences.leadingStatusSwipeActions)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
|
|
@ -97,9 +97,8 @@ extension MenuActionProvider {
|
||||||
}))
|
}))
|
||||||
elementHandler([UIMenu(title: "Add to List", image: UIImage(systemName: "list.bullet"), children: listActions)])
|
elementHandler([UIMenu(title: "Add to List", image: UIImage(systemName: "list.bullet"), children: listActions)])
|
||||||
}))
|
}))
|
||||||
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.hideReblogsAction(for: $0, mastodonController: $1) }))
|
|
||||||
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.muteAction(for: $0, mastodonController: $1) }))
|
|
||||||
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) }))
|
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) }))
|
||||||
|
suppressSection.append(relationshipAction(fetchRelationship, accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.muteAction(for: $0, mastodonController: $1) }))
|
||||||
suppressSection.append(createAction(identifier: "report", title: "Report", systemImageName: "flag", handler: { [unowned self] _ in
|
suppressSection.append(createAction(identifier: "report", title: "Report", systemImageName: "flag", handler: { [unowned self] _ in
|
||||||
let view = ReportView(report: EditedReport(accountID: accountID), mastodonController: mastodonController)
|
let view = ReportView(report: EditedReport(accountID: accountID), mastodonController: mastodonController)
|
||||||
let host = UIHostingController(rootView: view)
|
let host = UIHostingController(rootView: view)
|
||||||
|
@ -539,24 +538,6 @@ extension MenuActionProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
private func hideReblogsAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement {
|
|
||||||
let title = relationship.showingReblogs ? "Hide Reblogs" : "Show Reblogs"
|
|
||||||
// todo: need alternate repeat icon to use here
|
|
||||||
return UIAction(title: title, image: nil) { [weak self] _ in
|
|
||||||
let req = Account.setShowReblogs(relationship.accountID, showReblogs: !relationship.showingReblogs)
|
|
||||||
mastodonController.run(req) { response in
|
|
||||||
switch response {
|
|
||||||
case .failure(let error):
|
|
||||||
self?.handleError(error, title: "Error \(relationship.showingReblogs ? "Hiding" : "Showing") Reblogs")
|
|
||||||
case .success(let relationship, _):
|
|
||||||
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
|
|
||||||
self?.handleSuccess(title: relationship.showingReblogs ? "Reblogs Shown" : "Reblogs Hidden")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? {
|
private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? {
|
||||||
|
|
|
@ -127,15 +127,6 @@ class AttachmentView: GIFImageView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var badges: Badges = []
|
|
||||||
if attachment.description?.isEmpty == false {
|
|
||||||
badges.formUnion(.alt)
|
|
||||||
}
|
|
||||||
if attachment.kind == .gifv || attachment.url.pathExtension == "gif" {
|
|
||||||
badges.formUnion(.gif)
|
|
||||||
}
|
|
||||||
createBadgesView(badges)
|
|
||||||
|
|
||||||
switch attachment.kind {
|
switch attachment.kind {
|
||||||
case .image:
|
case .image:
|
||||||
loadImage()
|
loadImage()
|
||||||
|
@ -311,64 +302,6 @@ class AttachmentView: GIFImageView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createBadgesView(_ badges: Badges) {
|
|
||||||
guard !badges.isEmpty else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let stack = UIStackView()
|
|
||||||
stack.axis = .horizontal
|
|
||||||
stack.spacing = 2
|
|
||||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
|
|
||||||
let font = UIFontMetrics(forTextStyle: .caption1).scaledFont(for: .systemFont(ofSize: 13, weight: .bold))
|
|
||||||
func makeBadgeView(text: String) {
|
|
||||||
let container = UIView()
|
|
||||||
container.backgroundColor = .secondarySystemBackground.resolvedColor(with: UITraitCollection(userInterfaceStyle: .dark))
|
|
||||||
|
|
||||||
let label = UILabel()
|
|
||||||
label.font = font
|
|
||||||
label.adjustsFontForContentSizeCategory = true
|
|
||||||
label.textColor = .white
|
|
||||||
label.text = text
|
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
container.addSubview(label)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 2),
|
|
||||||
label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -2),
|
|
||||||
label.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),
|
|
||||||
label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -2),
|
|
||||||
])
|
|
||||||
stack.addArrangedSubview(container)
|
|
||||||
}
|
|
||||||
|
|
||||||
if badges.contains(.gif) {
|
|
||||||
makeBadgeView(text: "GIF")
|
|
||||||
}
|
|
||||||
if badges.contains(.alt) {
|
|
||||||
makeBadgeView(text: "ALT")
|
|
||||||
}
|
|
||||||
|
|
||||||
let first = stack.arrangedSubviews.first!
|
|
||||||
first.layer.masksToBounds = true
|
|
||||||
first.layer.cornerRadius = 4
|
|
||||||
if stack.arrangedSubviews.count > 1 {
|
|
||||||
first.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
|
||||||
let last = stack.arrangedSubviews.last!
|
|
||||||
last.layer.masksToBounds = true
|
|
||||||
last.layer.cornerRadius = 4
|
|
||||||
last.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
|
||||||
}
|
|
||||||
|
|
||||||
addSubview(stack)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4),
|
|
||||||
stack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Interaction
|
|
||||||
|
|
||||||
func showGallery() {
|
func showGallery() {
|
||||||
if let delegate = delegate,
|
if let delegate = delegate,
|
||||||
let gallery = delegate.attachmentViewGallery(startingAt: index) {
|
let gallery = delegate.attachmentViewGallery(startingAt: index) {
|
||||||
|
@ -395,13 +328,6 @@ fileprivate extension AttachmentView {
|
||||||
case gifData(URL, Data)
|
case gifData(URL, Data)
|
||||||
case cgImage(URL, CGImage)
|
case cgImage(URL, CGImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Badges: OptionSet {
|
|
||||||
static let gif = Badges(rawValue: 1 << 0)
|
|
||||||
static let alt = Badges(rawValue: 1 << 1)
|
|
||||||
|
|
||||||
let rawValue: Int
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AttachmentView: UIContextMenuInteractionDelegate {
|
extension AttachmentView: UIContextMenuInteractionDelegate {
|
||||||
|
|
|
@ -146,7 +146,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
do {
|
do {
|
||||||
_ = try await mastodonController.run(request)
|
_ = try await mastodonController.run(request)
|
||||||
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
self.actionButtonsStackView.isHidden = true
|
self.actionButtonsStackView.isHidden = true
|
||||||
self.addLabel(NSLocalizedString("Rejected", comment: "rejected follow request label"))
|
self.addLabel(NSLocalizedString("Rejected", comment: "rejected follow request label"))
|
||||||
} catch let error as Client.Error {
|
} catch let error as Client.Error {
|
||||||
|
@ -172,7 +172,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
do {
|
do {
|
||||||
_ = try await mastodonController.run(request)
|
_ = try await mastodonController.run(request)
|
||||||
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
self.actionButtonsStackView.isHidden = true
|
self.actionButtonsStackView.isHidden = true
|
||||||
self.addLabel(NSLocalizedString("Accepted", comment: "accepted follow request label"))
|
self.addLabel(NSLocalizedString("Accepted", comment: "accepted follow request label"))
|
||||||
} catch let error as Client.Error {
|
} catch let error as Client.Error {
|
||||||
|
|
|
@ -26,7 +26,7 @@ class PollOptionsView: UIControl {
|
||||||
private let animationDuration: TimeInterval = 0.1
|
private let animationDuration: TimeInterval = 0.1
|
||||||
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||||
|
|
||||||
private let generator = UISelectionFeedbackGenerator()
|
private let generator = UIImpactFeedbackGenerator(style: .soft)
|
||||||
|
|
||||||
override var isEnabled: Bool {
|
override var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -110,7 +110,7 @@ class PollOptionsView: UIControl {
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
|
|
||||||
generator.selectionChanged()
|
generator.impactOccurred()
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -139,7 +139,7 @@ class PollOptionsView: UIControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
if newIndex != nil {
|
if newIndex != nil {
|
||||||
generator.selectionChanged()
|
generator.impactOccurred()
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ class StatusPollView: UIView {
|
||||||
voteButton.isEnabled = false
|
voteButton.isEnabled = false
|
||||||
voteButton.disabledTitle = "Voted"
|
voteButton.disabledTitle = "Voted"
|
||||||
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
|
||||||
let request = Poll.vote(poll!.id, choices: optionsView.checkedOptionIndices)
|
let request = Poll.vote(poll!.id, choices: optionsView.checkedOptionIndices)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
|
|
|
@ -98,8 +98,8 @@ class StatusCardView: UIView {
|
||||||
hStack.spacing = 4
|
hStack.spacing = 4
|
||||||
hStack.clipsToBounds = true
|
hStack.clipsToBounds = true
|
||||||
hStack.layer.borderWidth = 0.5
|
hStack.layer.borderWidth = 0.5
|
||||||
|
hStack.layer.borderColor = UIColor.lightGray.cgColor
|
||||||
hStack.backgroundColor = inactiveBackgroundColor
|
hStack.backgroundColor = inactiveBackgroundColor
|
||||||
updateBorderColor()
|
|
||||||
|
|
||||||
addSubview(hStack)
|
addSubview(hStack)
|
||||||
|
|
||||||
|
@ -132,24 +132,11 @@ class StatusCardView: UIView {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
|
||||||
updateBorderColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
hStack.layer.cornerRadius = 0.1 * bounds.height
|
hStack.layer.cornerRadius = 0.1 * bounds.height
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateBorderColor() {
|
|
||||||
if traitCollection.userInterfaceStyle == .dark {
|
|
||||||
hStack.layer.borderColor = UIColor.darkGray.cgColor
|
|
||||||
} else {
|
|
||||||
hStack.layer.borderColor = UIColor.lightGray.cgColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUI(status: StatusMO) {
|
func updateUI(status: StatusMO) {
|
||||||
guard status.id != statusID else {
|
guard status.id != statusID else {
|
||||||
return
|
return
|
||||||
|
|
|
@ -83,21 +83,17 @@ extension StatusCollectionViewCell {
|
||||||
accountID = status.account.id
|
accountID = status.account.id
|
||||||
|
|
||||||
updateAccountUI(account: status.account)
|
updateAccountUI(account: status.account)
|
||||||
|
updateUIForPreferences(status: status)
|
||||||
|
|
||||||
contentContainer.contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
contentContainer.contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
||||||
contentContainer.contentTextView.navigationDelegate = delegate
|
contentContainer.contentTextView.navigationDelegate = delegate
|
||||||
contentContainer.attachmentsView.delegate = self
|
contentContainer.attachmentsView.delegate = self
|
||||||
contentContainer.attachmentsView.updateUI(status: status)
|
contentContainer.attachmentsView.updateUI(status: status)
|
||||||
if Preferences.shared.showLinkPreviews {
|
|
||||||
contentContainer.cardView.updateUI(status: status)
|
contentContainer.cardView.updateUI(status: status)
|
||||||
contentContainer.cardView.isHidden = status.card == nil
|
contentContainer.cardView.isHidden = status.card == nil
|
||||||
contentContainer.cardView.navigationDelegate = delegate
|
contentContainer.cardView.navigationDelegate = delegate
|
||||||
contentContainer.cardView.actionProvider = delegate
|
contentContainer.cardView.actionProvider = delegate
|
||||||
} else {
|
|
||||||
contentContainer.cardView.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUIForPreferences(status: status)
|
|
||||||
updateStatusState(status: status)
|
updateStatusState(status: status)
|
||||||
|
|
||||||
contentWarningLabel.text = status.spoilerText
|
contentWarningLabel.text = status.spoilerText
|
||||||
|
@ -154,12 +150,6 @@ extension StatusCollectionViewCell {
|
||||||
|
|
||||||
func baseUpdateUIForPreferences(status: StatusMO) {
|
func baseUpdateUIForPreferences(status: StatusMO) {
|
||||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.avatarImageViewSize
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.avatarImageViewSize
|
||||||
|
|
||||||
let newCardHidden = !Preferences.shared.showLinkPreviews || status.card == nil
|
|
||||||
if contentContainer.cardView.isHidden != newCardHidden {
|
|
||||||
delegate?.statusCellNeedsReconfigure(self, animated: false, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch Preferences.shared.attachmentBlurMode {
|
switch Preferences.shared.attachmentBlurMode {
|
||||||
case .never:
|
case .never:
|
||||||
contentContainer.attachmentsView.contentHidden = false
|
contentContainer.attachmentsView.contentHidden = false
|
||||||
|
|
Loading…
Reference in New Issue