Moderation Warning Notifications (IOS-264) (#1287)
* Add moderation-warning-notification-type (IOS-264) * Add entity for new AccountWarning (IOS-264) Details see https://github.com/mastodon/mastodon/pull/30065 for now, as there's no documentation (yet) * re-add file header (IOS-264) * Add authorization to instance-calls (IOS-264) This will improve using the app with `LIMITED_FEDERATION_INSTANCES` * Add basic cell for account-warnings (IOS-264) * Show some content for a warning (IOS-264) * Open strike in browser (IOS-264) * Add localization (IOS-264) * Add missing localization (IOS-264) * Cleanup (IOS-264)
This commit is contained in:
parent
677670055e
commit
794ffc002f
|
@ -729,6 +729,16 @@
|
|||
"accepted": "Accepted",
|
||||
"reject": "reject",
|
||||
"rejected": "Rejected"
|
||||
},
|
||||
"warning": {
|
||||
"none": "Your account has received a moderation warning.",
|
||||
"disable": "Your account has been disabled.",
|
||||
"mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.",
|
||||
"delete_statuses": "Some of your posts have been removed.",
|
||||
"sensitive": "Your posts will be marked as sensitive from now on.",
|
||||
"silence": "Your account has been limited.",
|
||||
"suspend": "Your account has been suspended.",
|
||||
"learn_more": "Learn More"
|
||||
}
|
||||
},
|
||||
"thread": {
|
||||
|
|
|
@ -729,6 +729,16 @@
|
|||
"accepted": "Accepted",
|
||||
"reject": "reject",
|
||||
"rejected": "Rejected"
|
||||
},
|
||||
"warning": {
|
||||
"none": "Your account has received a moderation warning.",
|
||||
"disable": "Your account has been disabled.",
|
||||
"mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.",
|
||||
"delete_statuses": "Some of your posts have been removed.",
|
||||
"sensitive": "Your posts will be marked as sensitive from now on.",
|
||||
"silence": "Your account has been limited.",
|
||||
"suspend": "Your account has been suspended.",
|
||||
"learn_more": "Learn More"
|
||||
}
|
||||
},
|
||||
"thread": {
|
||||
|
|
|
@ -162,6 +162,7 @@
|
|||
D87BFC8F291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87BFC8E291EC26A00FEE264 /* MastodonLoginServerTableViewCell.swift */; };
|
||||
D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */; };
|
||||
D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; };
|
||||
D8A0729D2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A0729C2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift */; };
|
||||
D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; };
|
||||
D8B5E4EE2A4EB8930008970C /* NotificationSettingTableViewToggleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4ED2A4EB8920008970C /* NotificationSettingTableViewToggleCell.swift */; };
|
||||
D8B5E4F02A4EB8A00008970C /* NotificationSettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4EF2A4EB8A00008970C /* NotificationSettingTableViewCell.swift */; };
|
||||
|
@ -796,6 +797,7 @@
|
|||
D87DC50E2A17C32F00219C5F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Intents.stringsdict; sourceTree = "<group>"; };
|
||||
D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSeparatorView.swift; sourceTree = "<group>"; };
|
||||
D8916DBF29211BE500124085 /* ContentSizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentSizedTableView.swift; sourceTree = "<group>"; };
|
||||
D8A0729C2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWarningNotificationCell.swift; sourceTree = "<group>"; };
|
||||
D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonLoginViewController.swift; sourceTree = "<group>"; };
|
||||
D8A6FE6129325F5900666A47 /* Intents.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Intents.stringsdict; sourceTree = "<group>"; };
|
||||
D8A6FE6229325F5900666A47 /* app.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = app.json; sourceTree = "<group>"; };
|
||||
|
@ -1484,6 +1486,7 @@
|
|||
DB023D2727A0FABD005AC798 /* NotificationTableViewCellDelegate.swift */,
|
||||
DB63F76E279A7D1100455B82 /* NotificationTableViewCell.swift */,
|
||||
DB63F774279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift */,
|
||||
D8A0729C2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1565,7 +1568,6 @@
|
|||
DB0617F927855B460030EE79 /* Profile */,
|
||||
DB4F097926A039C400D62E92 /* Status */,
|
||||
DB65C63527A2AF52008BAC2E /* Report */,
|
||||
DB0617F727855B010030EE79 /* Notification */,
|
||||
DB4F097726A039A200D62E92 /* Search */,
|
||||
);
|
||||
path = Diffable;
|
||||
|
@ -1921,15 +1923,6 @@
|
|||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB0617F727855B010030EE79 /* Notification */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2D35237926256D920031AF25 /* NotificationSection.swift */,
|
||||
2D7867182625B77500211898 /* NotificationItem.swift */,
|
||||
);
|
||||
path = Notification;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB0617F827855B170030EE79 /* User */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -2654,6 +2647,8 @@
|
|||
D80F627E2B5C32E400877059 /* NotificationView */,
|
||||
DB9D6BF725E4F5690051B173 /* NotificationViewController.swift */,
|
||||
2D607AD726242FC500B70763 /* NotificationViewModel.swift */,
|
||||
2D35237926256D920031AF25 /* NotificationSection.swift */,
|
||||
2D7867182625B77500211898 /* NotificationItem.swift */,
|
||||
);
|
||||
path = Notification;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3445,6 +3440,7 @@
|
|||
DB5B54AE2833C15F00DEF8B2 /* UserListViewModel+Diffable.swift in Sources */,
|
||||
DB482A3F261331E8008AE74C /* UserTimelineViewModel+State.swift in Sources */,
|
||||
DB3E6FE02806A4ED00B035AE /* DiscoveryHashtagsViewModel.swift in Sources */,
|
||||
D8A0729D2BEBA8D7001A4C7C /* AccountWarningNotificationCell.swift in Sources */,
|
||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||
DB0FCB7427956939006C02E2 /* DataSourceFacade+Status.swift in Sources */,
|
||||
D81A22782AB4782400905D71 /* SearchResultOverviewSection.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
FILEHEADER = " Copyright © ___YEAR___ Mastodon gGmbH. All rights reserved.";
|
||||
}
|
|
@ -131,7 +131,8 @@ final public class SceneCoordinator {
|
|||
from: from,
|
||||
transition: .show
|
||||
)
|
||||
|
||||
case .moderationWarning:
|
||||
break
|
||||
case ._other:
|
||||
assertionFailure()
|
||||
break
|
||||
|
|
|
@ -143,7 +143,7 @@ extension DataSourceFacade {
|
|||
else {
|
||||
return
|
||||
}
|
||||
let mentions = status.entity.mentions ?? []
|
||||
let mentions = status.entity.mentions
|
||||
|
||||
guard let mention = mentions.first(where: { $0.url == href }) else {
|
||||
_ = provider.coordinator.present(
|
||||
|
|
|
@ -43,6 +43,14 @@ extension UITableViewDelegate where Self: DataSourceProvider & AuthContextProvid
|
|||
target: .status, // remove reblog wrapper
|
||||
status: status
|
||||
)
|
||||
} else if let accountWarning = notification.entity.accountWarning {
|
||||
let url = Mastodon.API.disputesEndpoint(domain: authContext.mastodonAuthenticationBox.domain, strikeId: accountWarning.id)
|
||||
_ = coordinator.present(
|
||||
scene: .safari(url: url),
|
||||
from: self,
|
||||
transition: .safariPresent(animated: true, completion: nil)
|
||||
)
|
||||
|
||||
} else {
|
||||
await DataSourceFacade.coordinateToProfileScene(
|
||||
provider: self,
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
import MastodonAsset
|
||||
import MastodonLocalization
|
||||
|
||||
class AccountWarningNotificationCell: UITableViewCell {
|
||||
public static let reuseIdentifier = "AccountWarningNotificationCell"
|
||||
|
||||
let iconImageView: UIImageView
|
||||
let warningLabel: UILabel
|
||||
let learnMoreLabel: UILabel
|
||||
|
||||
private let contentStackView: UIStackView
|
||||
private let labelStackView: UIStackView
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
let icon = UIImage(systemName: "exclamationmark.triangle.fill")?
|
||||
.withConfiguration(UIImage.SymbolConfiguration(font: .systemFont(ofSize: 17)))
|
||||
iconImageView = UIImageView(image: icon)
|
||||
iconImageView.tintColor = Asset.Colors.Brand.blurple.color
|
||||
|
||||
warningLabel = UILabel()
|
||||
warningLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17))
|
||||
warningLabel.numberOfLines = 0
|
||||
|
||||
learnMoreLabel = UILabel()
|
||||
learnMoreLabel.text = L10n.Scene.Notification.Warning.learnMore
|
||||
learnMoreLabel.textColor = Asset.Colors.Brand.blurple.color
|
||||
learnMoreLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 17))
|
||||
learnMoreLabel.numberOfLines = 0
|
||||
|
||||
labelStackView = UIStackView(arrangedSubviews: [warningLabel, learnMoreLabel])
|
||||
labelStackView.axis = .vertical
|
||||
labelStackView.alignment = .leading
|
||||
labelStackView.spacing = 7
|
||||
|
||||
contentStackView = UIStackView(arrangedSubviews: [iconImageView, labelStackView])
|
||||
contentStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentStackView.axis = .horizontal
|
||||
contentStackView.alignment = .top
|
||||
contentStackView.spacing = 16
|
||||
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(contentStackView)
|
||||
setupConstraints()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
private func setupConstraints() {
|
||||
let constraints = [
|
||||
contentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
|
||||
contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
||||
contentView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor, constant: 16),
|
||||
contentView.bottomAnchor.constraint(equalTo: contentStackView.bottomAnchor, constant: 16),
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
}
|
||||
|
||||
public func configure(with accountWarning: Mastodon.Entity.AccountWarning) {
|
||||
warningLabel.text = accountWarning.action.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.AccountWarning.Action {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .none:
|
||||
return L10n.Scene.Notification.Warning.none
|
||||
case .disable:
|
||||
return L10n.Scene.Notification.Warning.disable
|
||||
case .markStatusesAsSensitive:
|
||||
return L10n.Scene.Notification.Warning.markStatusesAsSensitive
|
||||
case .deleteStatuses:
|
||||
return L10n.Scene.Notification.Warning.deleteStatuses
|
||||
case .sensitive:
|
||||
return L10n.Scene.Notification.Warning.sensitive
|
||||
case .silence:
|
||||
return L10n.Scene.Notification.Warning.silence
|
||||
case .suspend:
|
||||
return L10n.Scene.Notification.Warning.suspend
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,20 +37,28 @@ extension NotificationSection {
|
|||
configuration: Configuration
|
||||
) -> UITableViewDiffableDataSource<NotificationSection, NotificationItem> {
|
||||
tableView.register(NotificationTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationTableViewCell.self))
|
||||
tableView.register(AccountWarningNotificationCell.self, forCellReuseIdentifier: AccountWarningNotificationCell.reuseIdentifier)
|
||||
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||
|
||||
|
||||
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||
switch item {
|
||||
case .feed(let feed):
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: NotificationTableViewCell.ViewModel(value: .feed(feed)),
|
||||
configuration: configuration
|
||||
)
|
||||
return cell
|
||||
if let notification = feed.notification, let accountWarning = notification.accountWarning {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: AccountWarningNotificationCell.reuseIdentifier, for: indexPath) as! AccountWarningNotificationCell
|
||||
cell.configure(with: accountWarning)
|
||||
return cell
|
||||
} else {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
|
||||
configure(
|
||||
context: context,
|
||||
tableView: tableView,
|
||||
cell: cell,
|
||||
viewModel: NotificationTableViewCell.ViewModel(value: .feed(feed)),
|
||||
configuration: configuration
|
||||
)
|
||||
return cell
|
||||
}
|
||||
|
||||
case .feedLoader:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
||||
cell.activityIndicatorView.startAnimating()
|
|
@ -52,6 +52,9 @@ extension NotificationView {
|
|||
quoteStatusView.configure(status: status)
|
||||
setQuoteStatusViewDisplay()
|
||||
}
|
||||
case .moderationWarning:
|
||||
// case handled in `AccountWarningNotificationCell.swift`
|
||||
break
|
||||
case ._other:
|
||||
setAuthorContainerBottomPaddingViewDisplay()
|
||||
assertionFailure()
|
||||
|
|
|
@ -272,7 +272,7 @@ extension MastodonPickServerViewController {
|
|||
|
||||
authenticationViewModel.isAuthenticating.send(true)
|
||||
|
||||
context.apiService.instance(domain: server.domain)
|
||||
context.apiService.instance(domain: server.domain, authenticationBox: nil)
|
||||
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseFirst, Error>? in
|
||||
guard let self = self else { return nil }
|
||||
guard response.value.registrations != false else {
|
||||
|
|
|
@ -167,7 +167,7 @@ extension MastodonPickServerViewModel {
|
|||
self.unindexedServers.value = nil
|
||||
return self.context.apiService.webFinger(domain: domain)
|
||||
.flatMap { domain -> AnyPublisher<Result<Mastodon.Response.Content<[Mastodon.Entity.Server]>, Error>, Never> in
|
||||
return self.context.apiService.instance(domain: domain)
|
||||
return self.context.apiService.instance(domain: domain, authenticationBox: nil)
|
||||
.map { response -> Result<Mastodon.Response.Content<[Mastodon.Entity.Server]>, Error>in
|
||||
let newResponse = response.map { [Mastodon.Entity.Server(domain: domain, instance: $0)] }
|
||||
return Result.success(newResponse)
|
||||
|
|
|
@ -22,7 +22,7 @@ extension MastodonRegisterViewController {
|
|||
viewController.context = context
|
||||
viewController.coordinator = coordinator
|
||||
|
||||
let instanceResponse = try await context.apiService.instance(domain: domain).singleOutput()
|
||||
let instanceResponse = try await context.apiService.instance(domain: domain, authenticationBox: nil).singleOutput()
|
||||
let applicationResponse = try await context.apiService.createApplication(domain: domain).singleOutput()
|
||||
let accessTokenResponse = try await context.apiService.applicationAccessToken(
|
||||
domain: domain,
|
||||
|
|
|
@ -283,7 +283,7 @@ extension WelcomeViewController {
|
|||
|
||||
authenticationViewModel.isAuthenticating.send(true)
|
||||
|
||||
context.apiService.instance(domain: server.domain)
|
||||
context.apiService.instance(domain: server.domain, authenticationBox: nil)
|
||||
.compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseFirst, Error>? in
|
||||
guard let self = self else { return nil }
|
||||
guard response.value.registrations != false else {
|
||||
|
|
|
@ -67,7 +67,7 @@ class ReportViewModel {
|
|||
// bind server rules
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let response = try await context.apiService.instance(domain: authContext.mastodonAuthenticationBox.domain)
|
||||
let response = try await context.apiService.instance(domain: authContext.mastodonAuthenticationBox.domain, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.timeout(3, scheduler: DispatchQueue.main)
|
||||
.singleOutput()
|
||||
let rules = response.value.rules ?? []
|
||||
|
|
|
@ -75,7 +75,7 @@ extension SettingsCoordinator: SettingsViewControllerDelegate {
|
|||
let serverDetailsViewController = ServerDetailsViewController(domain: domain, appContext: appContext, authContext: authContext, sceneCoordinator: sceneCoordinator)
|
||||
serverDetailsViewController.delegate = self
|
||||
|
||||
appContext.apiService.instanceV2(domain: domain)
|
||||
appContext.apiService.instanceV2(domain: domain, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.sink { _ in
|
||||
|
||||
} receiveValue: { content in
|
||||
|
@ -83,7 +83,7 @@ extension SettingsCoordinator: SettingsViewControllerDelegate {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
appContext.apiService.extendedDescription(domain: domain)
|
||||
appContext.apiService.extendedDescription(domain: domain, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.sink { _ in
|
||||
|
||||
} receiveValue: { content in
|
||||
|
|
|
@ -14,18 +14,23 @@ import MastodonSDK
|
|||
extension APIService {
|
||||
|
||||
public func instance(
|
||||
domain: String
|
||||
domain: String,
|
||||
authenticationBox: MastodonAuthenticationBox?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Instance>, Error> {
|
||||
return Mastodon.API.Instance.instance(session: session, domain: domain)
|
||||
return Mastodon.API.Instance.instance(session: session, authorization: authenticationBox?.userAuthorization, domain: domain)
|
||||
}
|
||||
|
||||
public func instanceV2(
|
||||
domain: String
|
||||
domain: String,
|
||||
authenticationBox: MastodonAuthenticationBox?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.V2.Instance>, Error> {
|
||||
return Mastodon.API.V2.Instance.instance(session: session, domain: domain)
|
||||
return Mastodon.API.V2.Instance.instance(session: session, authorization: authenticationBox?.userAuthorization, domain: domain)
|
||||
}
|
||||
|
||||
public func extendedDescription(domain: String) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.ExtendedDescription>, Error> {
|
||||
return Mastodon.API.Instance.extendedDescription(session: session, domain: domain)
|
||||
public func extendedDescription(
|
||||
domain: String,
|
||||
authenticationBox: MastodonAuthenticationBox?
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.ExtendedDescription>, Error> {
|
||||
return Mastodon.API.Instance.extendedDescription(session: session, authorization: authenticationBox?.userAuthorization, domain: domain)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ extension APIService {
|
|||
.favourite,
|
||||
.poll,
|
||||
.status,
|
||||
.moderationWarning
|
||||
]
|
||||
case .mentions:
|
||||
return [
|
||||
|
|
|
@ -45,11 +45,11 @@ public final class InstanceService {
|
|||
|
||||
extension InstanceService {
|
||||
func updateInstance(domain: String) {
|
||||
guard let apiService = self.apiService else { return }
|
||||
apiService.instance(domain: domain)
|
||||
guard let apiService else { return }
|
||||
apiService.instance(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first)
|
||||
.flatMap { [unowned self] response -> AnyPublisher<Void, Error> in
|
||||
if response.value.version?.majorServerVersion(greaterThanOrEquals: 4) == true {
|
||||
return apiService.instanceV2(domain: domain)
|
||||
return apiService.instanceV2(domain: domain, authenticationBox: authenticationService?.mastodonAuthenticationBoxes.first)
|
||||
.flatMap { return self.updateInstanceV2(domain: domain, response: $0) }
|
||||
.eraseToAnyPublisher()
|
||||
} else {
|
||||
|
|
|
@ -909,6 +909,24 @@ public enum L10n {
|
|||
/// Mentions
|
||||
public static let mentions = L10n.tr("Localizable", "Scene.Notification.Title.Mentions", fallback: "Mentions")
|
||||
}
|
||||
public enum Warning {
|
||||
/// Some of your posts have been removed.
|
||||
public static let deleteStatuses = L10n.tr("Localizable", "Scene.Notification.Warning.DeleteStatuses", fallback: "Some of your posts have been removed.")
|
||||
/// Your account has been disabled.
|
||||
public static let disable = L10n.tr("Localizable", "Scene.Notification.Warning.Disable", fallback: "Your account has been disabled.")
|
||||
/// Learn More
|
||||
public static let learnMore = L10n.tr("Localizable", "Scene.Notification.Warning.LearnMore", fallback: "Learn More")
|
||||
/// Some of your posts have been marked as sensitive.
|
||||
public static let markStatusesAsSensitive = L10n.tr("Localizable", "Scene.Notification.Warning.MarkStatusesAsSensitive", fallback: "Some of your posts have been marked as sensitive.")
|
||||
/// Your account has received a moderation warning.
|
||||
public static let `none` = L10n.tr("Localizable", "Scene.Notification.Warning.None", fallback: "Your account has received a moderation warning.")
|
||||
/// Your posts will be marked as sensitive from now on.
|
||||
public static let sensitive = L10n.tr("Localizable", "Scene.Notification.Warning.Sensitive", fallback: "Your posts will be marked as sensitive from now on.")
|
||||
/// Your account has been limited.
|
||||
public static let silence = L10n.tr("Localizable", "Scene.Notification.Warning.Silence", fallback: "Your account has been limited.")
|
||||
/// Your account has been suspended.
|
||||
public static let suspend = L10n.tr("Localizable", "Scene.Notification.Warning.Suspend", fallback: "Your account has been suspended.")
|
||||
}
|
||||
}
|
||||
public enum Preview {
|
||||
public enum Keyboard {
|
||||
|
|
|
@ -323,6 +323,14 @@ uploaded to Mastodon.";
|
|||
"Scene.Notification.NotificationDescription.RequestToFollowYou" = "request to follow you";
|
||||
"Scene.Notification.Title.Everything" = "Everything";
|
||||
"Scene.Notification.Title.Mentions" = "Mentions";
|
||||
"Scene.Notification.Warning.DeleteStatuses" = "Some of your posts have been removed.";
|
||||
"Scene.Notification.Warning.Disable" = "Your account has been disabled.";
|
||||
"Scene.Notification.Warning.LearnMore" = "Learn More";
|
||||
"Scene.Notification.Warning.MarkStatusesAsSensitive" = "Some of your posts have been marked as sensitive.";
|
||||
"Scene.Notification.Warning.None" = "Your account has received a moderation warning.";
|
||||
"Scene.Notification.Warning.Sensitive" = "Your posts will be marked as sensitive from now on.";
|
||||
"Scene.Notification.Warning.Silence" = "Your account has been limited.";
|
||||
"Scene.Notification.Warning.Suspend" = "Your account has been suspended.";
|
||||
"Scene.Preview.Keyboard.ClosePreview" = "Close Preview";
|
||||
"Scene.Preview.Keyboard.ShowNext" = "Show Next";
|
||||
"Scene.Preview.Keyboard.ShowPrevious" = "Show Previous";
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Mastodon.API {
|
||||
public static func disputesEndpoint(domain: String, strikeId: String) -> URL {
|
||||
return Mastodon.API.webURL(domain: domain).appendingPathComponent("disputes/strikes/\(strikeId)")
|
||||
}
|
||||
}
|
|
@ -28,9 +28,10 @@ extension Mastodon.API.Instance {
|
|||
/// - Returns: `AnyPublisher` contains `Instance` nested in the response
|
||||
public static func instance(
|
||||
session: URLSession,
|
||||
authorization: Mastodon.API.OAuth.Authorization?,
|
||||
domain: String
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Instance>, Error> {
|
||||
let request = Mastodon.API.get(url: instanceEndpointURL(domain: domain))
|
||||
let request = Mastodon.API.get(url: instanceEndpointURL(domain: domain), authorization: authorization)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value: Mastodon.Entity.Instance
|
||||
|
@ -62,9 +63,10 @@ extension Mastodon.API.Instance {
|
|||
/// [Document](https://docs.joinmastodon.org/methods/instance/#extended_description)
|
||||
public static func extendedDescription(
|
||||
session: URLSession,
|
||||
authorization: Mastodon.API.OAuth.Authorization?,
|
||||
domain: String
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.ExtendedDescription>, Error> {
|
||||
let request = Mastodon.API.get(url: extendedDescriptionEndpointURL(domain: domain))
|
||||
let request = Mastodon.API.get(url: extendedDescriptionEndpointURL(domain: domain), authorization: authorization)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: Mastodon.Entity.ExtendedDescription.self, from: data, response: response)
|
||||
|
|
|
@ -21,12 +21,13 @@ extension Mastodon.API.V2.Instance {
|
|||
/// - Returns: `AnyPublisher` contains `Instance` nested in the response
|
||||
public static func instance(
|
||||
session: URLSession,
|
||||
authorization: Mastodon.API.OAuth.Authorization?,
|
||||
domain: String
|
||||
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.V2.Instance>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: instanceEndpointURL(domain: domain),
|
||||
query: nil,
|
||||
authorization: nil
|
||||
authorization: authorization
|
||||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
|
|
|
@ -99,6 +99,10 @@ extension Mastodon.API {
|
|||
public static func profileSettingsURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/auth/edit")!
|
||||
}
|
||||
|
||||
public static func webURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/")!
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.API {
|
||||
|
@ -211,7 +215,7 @@ extension Mastodon.API {
|
|||
return try Mastodon.API.decoder.decode(type, from: data)
|
||||
} catch let decodeError {
|
||||
#if DEBUG
|
||||
debugPrint("\(response.url), Data: \(String(data: data, encoding: .utf8)), \(decodeError)")
|
||||
debugPrint("URL: \(String(describing: response.url))\nData: \(String(data: data, encoding: .utf8) ?? "-")\nError:\(decodeError)\n----\n")
|
||||
#endif
|
||||
|
||||
guard let httpURLResponse = response as? HTTPURLResponse else {
|
||||
|
|
|
@ -8,7 +8,7 @@ extension Mastodon.Entity {
|
|||
/// ## Reference:
|
||||
/// [Document](https://docs.joinmastodon.org/entities/ExtendedDescription/)
|
||||
public struct ExtendedDescription: Codable {
|
||||
public let updatedAt: Date
|
||||
public let updatedAt: Date?
|
||||
public let content: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
|
|
|
@ -23,15 +23,69 @@ extension Mastodon.Entity {
|
|||
public let type: Type
|
||||
public let createdAt: Date
|
||||
public let account: Account
|
||||
|
||||
public let status: Status?
|
||||
|
||||
public let accountWarning: AccountWarning?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case type
|
||||
case createdAt = "created_at"
|
||||
case account
|
||||
case status
|
||||
case accountWarning = "moderation_warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity {
|
||||
public struct AccountWarning: Codable {
|
||||
public typealias ID = String
|
||||
|
||||
public let id: ID
|
||||
public let action: Action
|
||||
public let text: String?
|
||||
public let targetAccount: Account
|
||||
public let appeal: Appeal?
|
||||
public let statusIds: [Mastodon.Entity.Status.ID]?
|
||||
|
||||
public enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case action
|
||||
case text
|
||||
case targetAccount = "target_account"
|
||||
case appeal
|
||||
case statusIds = "status_ids"
|
||||
}
|
||||
|
||||
public enum Action: String, Codable {
|
||||
case none
|
||||
case disable
|
||||
case markStatusesAsSensitive
|
||||
case deleteStatuses
|
||||
case sensitive
|
||||
case silence
|
||||
case suspend
|
||||
|
||||
public enum CodingKeys: String, CodingKey {
|
||||
case none
|
||||
case disable
|
||||
case markStatusesAsSensitive = "mark_statuses_as_sensitive"
|
||||
case deleteStatuses = "delete_statuses"
|
||||
case sensitive
|
||||
case silence
|
||||
case suspend
|
||||
}
|
||||
}
|
||||
|
||||
public struct Appeal: Codable {
|
||||
public let text: String
|
||||
public let state: State
|
||||
|
||||
public enum State: String, Codable {
|
||||
case approved
|
||||
case rejected
|
||||
case pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,21 +100,10 @@ extension Mastodon.Entity.Notification {
|
|||
case favourite
|
||||
case poll
|
||||
case status
|
||||
|
||||
case moderationWarning
|
||||
|
||||
case _other(String)
|
||||
|
||||
public static var knownCases: [NotificationType] {
|
||||
return [
|
||||
.follow,
|
||||
.followRequest,
|
||||
.mention,
|
||||
.reblog,
|
||||
.favourite,
|
||||
.poll,
|
||||
.status
|
||||
]
|
||||
}
|
||||
|
||||
public init?(rawValue: String) {
|
||||
switch rawValue {
|
||||
case "follow": self = .follow
|
||||
|
@ -70,6 +113,7 @@ extension Mastodon.Entity.Notification {
|
|||
case "favourite": self = .favourite
|
||||
case "poll": self = .poll
|
||||
case "status": self = .status
|
||||
case "moderation_warning": self = .moderationWarning
|
||||
default: self = ._other(rawValue)
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +127,7 @@ extension Mastodon.Entity.Notification {
|
|||
case .favourite: return "favourite"
|
||||
case .poll: return "poll"
|
||||
case .status: return "status"
|
||||
case .moderationWarning: return "moderation_warning"
|
||||
case ._other(let value): return value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,12 @@ private extension ActionRequestHandler {
|
|||
func continueWithSearch(_ query: String) {
|
||||
guard
|
||||
let url = URL(string: query),
|
||||
let host = url.host
|
||||
let host = url.host,
|
||||
let activeAuthenticationBox = Self.appContext
|
||||
.authenticationService
|
||||
.mastodonAuthenticationBoxes
|
||||
.first
|
||||
|
||||
else {
|
||||
return doneWithInvalidLink()
|
||||
}
|
||||
|
@ -119,6 +124,7 @@ private extension ActionRequestHandler {
|
|||
.Instance
|
||||
.instance(
|
||||
session: .shared,
|
||||
authorization: activeAuthenticationBox.userAuthorization,
|
||||
domain: host
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
|
Loading…
Reference in New Issue