forked from shadowfacts/Tusker
Rewrite Drafts screen with SwiftUI
This commit is contained in:
parent
d0f8691560
commit
6d692c2730
|
@ -76,10 +76,6 @@
|
|||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; };
|
||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
||||
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF75217E923E00CC0648 /* DraftsManager.swift */; };
|
||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF78217E950100CC0648 /* DraftsTableViewController.xib */; };
|
||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; };
|
||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; };
|
||||
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */; };
|
||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5221EA708700FE4B39 /* StatusFormat.swift */; };
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
|
||||
|
@ -257,6 +253,8 @@
|
|||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */; };
|
||||
D6BEA249291C6118002F4D01 /* DraftsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA248291C6118002F4D01 /* DraftsView.swift */; };
|
||||
D6BEA24B291C6A2B002F4D01 /* AlertWithData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA24A291C6A2B002F4D01 /* AlertWithData.swift */; };
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */; };
|
||||
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C143D9253510F4007DC240 /* ComposeEmojiTextField.swift */; };
|
||||
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
||||
|
@ -432,10 +430,6 @@
|
|||
D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListAccountsViewController.swift; sourceTree = "<group>"; };
|
||||
D627FF75217E923E00CC0648 /* DraftsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsManager.swift; sourceTree = "<group>"; };
|
||||
D627FF78217E950100CC0648 /* DraftsTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftsTableViewController.xib; sourceTree = "<group>"; };
|
||||
D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsTableViewController.swift; sourceTree = "<group>"; };
|
||||
D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6285B5221EA708700FE4B39 /* StatusFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFormat.swift; sourceTree = "<group>"; };
|
||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
|
||||
|
@ -615,6 +609,8 @@
|
|||
D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = "<group>"; };
|
||||
D6BEA243291A0C83002F4D01 /* Duckable */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Duckable; path = Packages/Duckable; sourceTree = "<group>"; };
|
||||
D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duckable+Root.swift"; sourceTree = "<group>"; };
|
||||
D6BEA248291C6118002F4D01 /* DraftsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsView.swift; sourceTree = "<group>"; };
|
||||
D6BEA24A291C6A2B002F4D01 /* AlertWithData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertWithData.swift; sourceTree = "<group>"; };
|
||||
D6BED173212667E900F02DA0 /* TimelineStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6C143D9253510F4007DC240 /* ComposeEmojiTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeEmojiTextField.swift; sourceTree = "<group>"; };
|
||||
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCardView.swift; sourceTree = "<group>"; };
|
||||
|
@ -743,15 +739,6 @@
|
|||
path = "Hashtag Cell";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D61959D0241E842400A37B8E /* Draft Cell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */,
|
||||
D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */,
|
||||
);
|
||||
path = "Draft Cell";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D61959D2241E846D00A37B8E /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -850,15 +837,6 @@
|
|||
path = Lists;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D627FF77217E94F200CC0648 /* Drafts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D627FF78217E950100CC0648 /* DraftsTableViewController.xib */,
|
||||
D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */,
|
||||
);
|
||||
path = Drafts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D62D241E217AA46B005076CC /* Shortcuts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -906,7 +884,6 @@
|
|||
D641C787213DD862004B4513 /* Compose */,
|
||||
D641C785213DD83B004B4513 /* Conversation */,
|
||||
D6F2E960249E772F005846BB /* Crash Reporter */,
|
||||
D627FF77217E94F200CC0648 /* Drafts */,
|
||||
D627943C23A5635D00D38C68 /* Explore */,
|
||||
D6A4DCC92553666600D9DE31 /* Fast Account Switcher */,
|
||||
D641C788213DD86D004B4513 /* Large Image */,
|
||||
|
@ -1011,6 +988,7 @@
|
|||
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */,
|
||||
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */,
|
||||
D6C143D9253510F4007DC240 /* ComposeEmojiTextField.swift */,
|
||||
D6BEA248291C6118002F4D01 /* DraftsView.swift */,
|
||||
);
|
||||
path = Compose;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1284,6 +1262,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
|
||||
D6BEA24A291C6A2B002F4D01 /* AlertWithData.swift */,
|
||||
D68E6F5E253C9B2D001A1B4C /* BaseEmojiLabel.swift */,
|
||||
D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */,
|
||||
D6895DC328D65342006341DA /* ConfirmReblogStatusPreviewView.swift */,
|
||||
|
@ -1312,7 +1291,6 @@
|
|||
D626494023C122C800612E6E /* Asset Picker */,
|
||||
D6C7D27B22B6EBE200071952 /* Attachments */,
|
||||
D6DEA0DB268400AF00FE896A /* Confirm Load More Cell */,
|
||||
D61959D0241E842400A37B8E /* Draft Cell */,
|
||||
D611C2CC232DC5FC00C86A49 /* Hashtag Cell */,
|
||||
D61AC1DA232EA43100C54D2D /* Instance Cell */,
|
||||
D641C78C213DD937004B4513 /* Notifications */,
|
||||
|
@ -1670,7 +1648,6 @@
|
|||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */,
|
||||
D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */,
|
||||
D6A3BC8B2321F79B00FD64D5 /* AccountTableViewCell.xib in Resources */,
|
||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */,
|
||||
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */,
|
||||
D6412B0B24B0D4C600F5412E /* ProfileHeaderView.xib in Resources */,
|
||||
D6C82B5725C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib in Resources */,
|
||||
|
@ -1682,7 +1659,6 @@
|
|||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */,
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */,
|
||||
D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */,
|
||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */,
|
||||
|
@ -1861,7 +1837,7 @@
|
|||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */,
|
||||
D62275A024F1677200B82A16 /* ComposeHostingController.swift in Sources */,
|
||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
||||
D6BEA249291C6118002F4D01 /* DraftsView.swift in Sources */,
|
||||
D6403CC224A6B72D00E81C55 /* VisualEffectImageButton.swift in Sources */,
|
||||
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */,
|
||||
D673ACCE2919E74200D6F8B0 /* MenuPicker.swift in Sources */,
|
||||
|
@ -1875,7 +1851,6 @@
|
|||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
|
||||
D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */,
|
||||
D61DC84628F498F200B82C6E /* Logging.swift in Sources */,
|
||||
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
|
||||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
|
||||
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */,
|
||||
D6DF95C12533F5DE0027A9B6 /* RelationshipMO.swift in Sources */,
|
||||
|
@ -1933,6 +1908,7 @@
|
|||
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */,
|
||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
||||
D6BEA24B291C6A2B002F4D01 /* AlertWithData.swift in Sources */,
|
||||
D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */,
|
||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */,
|
||||
|
|
|
@ -107,6 +107,8 @@ extension Draft: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
extension Draft: Identifiable {}
|
||||
|
||||
extension Draft {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class DraftsManager: Codable {
|
||||
class DraftsManager: Codable, ObservableObject {
|
||||
|
||||
private(set) static var shared: DraftsManager = load()
|
||||
|
||||
|
@ -48,7 +48,12 @@ class DraftsManager: Codable {
|
|||
}
|
||||
}
|
||||
|
||||
private var drafts: [UUID: Draft] = [:]
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(drafts, forKey: .drafts)
|
||||
}
|
||||
|
||||
@Published private var drafts: [UUID: Draft] = [:]
|
||||
var sorted: [Draft] {
|
||||
return drafts.values.sorted(by: { $0.lastModified > $1.lastModified })
|
||||
}
|
||||
|
|
|
@ -335,9 +335,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView>, Ducka
|
|||
}
|
||||
|
||||
@objc func draftsButtonPresed() {
|
||||
let draftsVC = DraftsTableViewController(account: mastodonController.accountInfo!, exclude: draft)
|
||||
draftsVC.delegate = self
|
||||
present(UINavigationController(rootViewController: draftsVC), animated: true)
|
||||
uiState.isShowingDraftsList = true
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -377,6 +375,16 @@ extension ComposeHostingController: ComposeUIStateDelegate {
|
|||
|
||||
present(ComposeDrawingNavigationController(editing: drawing, delegate: self), animated: true)
|
||||
}
|
||||
|
||||
func selectDraft(_ draft: Draft) {
|
||||
if self.draft.hasContent {
|
||||
DraftsManager.save()
|
||||
} else {
|
||||
DraftsManager.shared.remove(self.draft)
|
||||
}
|
||||
uiState.draft = draft
|
||||
uiState.isShowingDraftsList = false
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeHostingController: AssetPickerViewControllerDelegate {
|
||||
|
@ -403,42 +411,6 @@ extension ComposeHostingController: AssetPickerViewControllerDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension ComposeHostingController: DraftsTableViewControllerDelegate {
|
||||
func draftSelectionCanceled() {
|
||||
}
|
||||
|
||||
func shouldSelectDraft(_ draft: Draft, completion: @escaping (Bool) -> Void) {
|
||||
if draft.inReplyToID != self.draft.inReplyToID,
|
||||
self.draft.hasContent {
|
||||
let alertController = UIAlertController(title: "Different Reply", message: "The selected draft is a reply to a different status, do you wish to use it?", preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
|
||||
completion(false)
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: "Restore Draft", style: .default, handler: { (_) in
|
||||
completion(true)
|
||||
}))
|
||||
// we can't present the laert ourselves since the compose VC is already presenting the draft selector
|
||||
// but presenting on the presented view controller seems hacky, is there a better way to do this?
|
||||
presentedViewController!.present(alertController, animated: true)
|
||||
} else {
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
|
||||
func draftSelected(_ draft: Draft) {
|
||||
if self.draft.hasContent {
|
||||
DraftsManager.save()
|
||||
} else {
|
||||
DraftsManager.shared.remove(self.draft)
|
||||
}
|
||||
|
||||
uiState.draft = draft
|
||||
}
|
||||
|
||||
func draftSelectionCompleted() {
|
||||
}
|
||||
}
|
||||
|
||||
// superseded by duckable stuff
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
extension ComposeHostingController: UIAdaptivePresentationControllerDelegate {
|
||||
|
|
|
@ -15,6 +15,7 @@ protocol ComposeUIStateDelegate: AnyObject {
|
|||
// @available(iOS, obsoleted: 16.0)
|
||||
func presentAssetPickerSheet()
|
||||
func presentComposeDrawing()
|
||||
func selectDraft(_ draft: Draft)
|
||||
|
||||
func keyboardWillShow(accessoryView: UIView, notification: Notification)
|
||||
func keyboardWillHide(accessoryView: UIView, notification: Notification)
|
||||
|
@ -27,6 +28,7 @@ class ComposeUIState: ObservableObject {
|
|||
|
||||
@Published var draft: Draft
|
||||
@Published var isShowingSaveDraftSheet = false
|
||||
@Published var isShowingDraftsList = false
|
||||
@Published var attachmentsMissingDescriptions = Set<UUID>()
|
||||
@Published var autocompleteState: AutocompleteState? = nil
|
||||
|
||||
|
|
|
@ -98,6 +98,9 @@ struct ComposeView: View {
|
|||
}
|
||||
})
|
||||
.navigationTitle(navTitle)
|
||||
.sheet(isPresented: $uiState.isShowingDraftsList) {
|
||||
DraftsView(currentDraft: draft)
|
||||
}
|
||||
.actionSheet(isPresented: $uiState.isShowingSaveDraftSheet, content: self.saveAndCloseSheet)
|
||||
.alert(isPresented: $isShowingPostErrorAlert) {
|
||||
Alert(
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
//
|
||||
// DraftsView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 11/9/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DraftsView: View {
|
||||
let currentDraft: Draft
|
||||
@EnvironmentObject var uiState: ComposeUIState
|
||||
@EnvironmentObject var mastodonController: MastodonController
|
||||
@StateObject private var draftsManager = DraftsManager.shared
|
||||
@State private var draftForDifferentReply: Draft?
|
||||
|
||||
private var visibleDrafts: [Draft] {
|
||||
draftsManager.sorted.filter {
|
||||
$0.accountID == mastodonController.accountInfo!.id && $0.id != currentDraft.id
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
ForEach(visibleDrafts) { draft in
|
||||
Button {
|
||||
maybeSelectDraft(draft)
|
||||
} label: {
|
||||
DraftView(draft: draft)
|
||||
}
|
||||
.onDrag {
|
||||
let activity = UserActivityManager.editDraftActivity(id: draft.id, accountID: mastodonController.accountInfo!.id)
|
||||
activity.displaysAuxiliaryScene = true
|
||||
return NSItemProvider(object: activity)
|
||||
}
|
||||
}
|
||||
.onDelete { indices in
|
||||
indices
|
||||
.map { visibleDrafts[$0] }
|
||||
.forEach { draftsManager.remove($0) }
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(Text("Drafts"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
uiState.isShowingDraftsList = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alertWithData("Different Reply", data: $draftForDifferentReply) { draft in
|
||||
Button("Cancel", role: .cancel) {
|
||||
draftForDifferentReply = nil
|
||||
}
|
||||
Button("Restore Draft") {
|
||||
uiState.delegate?.selectDraft(draft)
|
||||
}
|
||||
} message: { draft in
|
||||
Text("The selected draft is a reply to a different post, do you wish to use it?")
|
||||
}
|
||||
}
|
||||
|
||||
private func maybeSelectDraft(_ draft: Draft) {
|
||||
if draft.inReplyToID != currentDraft.inReplyToID,
|
||||
currentDraft.hasContent {
|
||||
draftForDifferentReply = draft
|
||||
} else {
|
||||
uiState.delegate?.selectDraft(draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DraftView: View {
|
||||
@ObservedObject private var draft: Draft
|
||||
|
||||
init(draft: Draft) {
|
||||
self._draft = ObservedObject(wrappedValue: draft)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
if draft.contentWarningEnabled {
|
||||
Text(draft.contentWarning)
|
||||
.font(.body.bold())
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text(draft.text)
|
||||
.font(.body)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
ForEach(draft.attachments) { attachment in
|
||||
ComposeAttachmentImage(attachment: attachment, fullSize: false)
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(draft.lastModified.timeAgoString())
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DraftsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DraftsView(currentDraft: Draft(accountID: ""))
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
//
|
||||
// DraftsTableViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/22/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol DraftsTableViewControllerDelegate: AnyObject {
|
||||
func draftSelectionCanceled()
|
||||
func shouldSelectDraft(_ draft: Draft, completion: @escaping (Bool) -> Void)
|
||||
func draftSelected(_ draft: Draft)
|
||||
func draftSelectionCompleted()
|
||||
}
|
||||
|
||||
class DraftsTableViewController: UITableViewController {
|
||||
|
||||
let account: LocalData.UserAccountInfo
|
||||
let excludedDraft: Draft?
|
||||
weak var delegate: DraftsTableViewControllerDelegate?
|
||||
|
||||
var drafts = [Draft]()
|
||||
|
||||
init(account: LocalData.UserAccountInfo, exclude: Draft? = nil) {
|
||||
self.account = account
|
||||
self.excludedDraft = exclude
|
||||
|
||||
super.init(nibName: "DraftsTableViewController", bundle: nil)
|
||||
|
||||
title = "Drafts"
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelPressed))
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 140
|
||||
|
||||
tableView.register(UINib(nibName: "DraftTableViewCell", bundle: nil), forCellReuseIdentifier: "draftCell")
|
||||
|
||||
tableView.dragDelegate = self
|
||||
|
||||
drafts = DraftsManager.shared.sorted.filter { (draft) in
|
||||
draft.accountID == account.id && draft != excludedDraft
|
||||
}
|
||||
}
|
||||
|
||||
func draft(for indexPath: IndexPath) -> Draft {
|
||||
return drafts[indexPath.row]
|
||||
}
|
||||
|
||||
// MARK: - Table View Data Source
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return drafts.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "draftCell", for: indexPath) as? DraftTableViewCell else { fatalError() }
|
||||
|
||||
cell.updateUI(for: draft(for: indexPath))
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let draft = self.draft(for: indexPath)
|
||||
func select() {
|
||||
delegate?.draftSelected(draft)
|
||||
dismiss(animated: true) {
|
||||
self.delegate?.draftSelectionCompleted()
|
||||
}
|
||||
}
|
||||
if let delegate = delegate {
|
||||
delegate.shouldSelectDraft(draft) { (shouldSelect) in
|
||||
if shouldSelect {
|
||||
select()
|
||||
} else {
|
||||
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
select()
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
|
||||
return .delete
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
guard editingStyle == .delete else { return }
|
||||
DraftsManager.shared.remove(draft(for: indexPath))
|
||||
drafts.remove(at: indexPath.row)
|
||||
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
return UIContextMenuConfiguration(actionProvider: { _ in
|
||||
return UIMenu(children: [
|
||||
UIAction(title: "Delete Draft", image: UIImage(systemName: "trash"), attributes: .destructive, handler: { [unowned self] _ in
|
||||
DraftsManager.shared.remove(self.draft(for: indexPath))
|
||||
drafts.remove(at: indexPath.row)
|
||||
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
})
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
@objc func cancelPressed() {
|
||||
delegate?.draftSelectionCanceled()
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DraftsTableViewController: UITableViewDragDelegate {
|
||||
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
let draft = self.draft(for: indexPath)
|
||||
let activity = UserActivityManager.editDraftActivity(id: draft.id, accountID: account.id)
|
||||
activity.displaysAuxiliaryScene = true
|
||||
let provider = NSItemProvider(object: activity)
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14810.11" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14766.13"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="DraftsTableViewController" customModule="Tusker" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="view" destination="O5v-ea-iTS" id="sft-3K-LZf"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="O5v-ea-iTS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<point key="canvasLocation" x="-302" y="87"/>
|
||||
</tableView>
|
||||
</objects>
|
||||
</document>
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// AlertWithData.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 11/9/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AlertWithData<Data, A: View, M: View>: ViewModifier {
|
||||
let title: LocalizedStringKey
|
||||
@Binding var data: Data?
|
||||
let actions: (Data) -> A
|
||||
let message: (Data) -> M
|
||||
|
||||
private var isPresented: Binding<Bool> {
|
||||
Binding(get: {
|
||||
data != nil
|
||||
}, set: { newValue in
|
||||
guard !newValue else {
|
||||
fatalError("Cannot set isPresented to true without data")
|
||||
}
|
||||
data = nil
|
||||
})
|
||||
}
|
||||
|
||||
init(title: LocalizedStringKey, data: Binding<Data?>, @ViewBuilder actions: @escaping (Data) -> A, @ViewBuilder message: @escaping (Data) -> M) {
|
||||
self.title = title
|
||||
self._data = data
|
||||
self.actions = actions
|
||||
self.message = message
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.alert(title, isPresented: isPresented, presenting: data, actions: actions, message: message)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func alertWithData<Data, A: View, M: View>(_ title: LocalizedStringKey, data: Binding<Data?>, @ViewBuilder actions: @escaping (Data) -> A, @ViewBuilder message: @escaping (Data) -> M) -> some View {
|
||||
modifier(AlertWithData(title: title, data: data, actions: actions, message: message))
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
//
|
||||
// DraftsTableViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/22/18.
|
||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
class DraftTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var contentWarningLabel: UILabel!
|
||||
@IBOutlet weak var contentLabel: UILabel!
|
||||
@IBOutlet weak var lastModifiedLabel: UILabel!
|
||||
@IBOutlet weak var attachmentsStackViewContainer: UIView!
|
||||
@IBOutlet weak var attachmentsStackView: UIStackView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
contentView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
|
||||
|
||||
contentWarningLabel.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))
|
||||
contentWarningLabel.adjustsFontForContentSizeCategory = true
|
||||
}
|
||||
|
||||
func updateUI(for draft: Draft) {
|
||||
contentWarningLabel.text = draft.contentWarning
|
||||
contentWarningLabel.isHidden = !draft.contentWarningEnabled
|
||||
contentLabel.text = draft.text
|
||||
lastModifiedLabel.text = draft.lastModified.timeAgoString()
|
||||
|
||||
attachmentsStackViewContainer.isHidden = draft.attachments.count == 0
|
||||
|
||||
for attachment in draft.attachments {
|
||||
let size = CGSize(width: 50, height: 50)
|
||||
let imageView = UIImageView(frame: CGRect(origin: .zero, size: size))
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = 5
|
||||
attachmentsStackView.addArrangedSubview(imageView)
|
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
|
||||
imageView.backgroundColor = .secondarySystemBackground
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
|
||||
switch attachment.data {
|
||||
case let .asset(asset):
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: nil) { (image, _) in
|
||||
imageView.image = image
|
||||
}
|
||||
case let .image(image):
|
||||
imageView.image = image
|
||||
case .video(_):
|
||||
// videos aren't saved to drafts, so this is unreachable
|
||||
return
|
||||
case let .drawing(drawing):
|
||||
imageView.image = drawing.imageInLightMode(from: drawing.bounds)
|
||||
imageView.backgroundColor = .white
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
attachmentsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="143" id="Q7N-Mt-RPF" customClass="DraftTableViewCell" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="143"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Q7N-Mt-RPF" id="KVi-jA-AET">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="143"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="gaD-3B-qO1">
|
||||
<rect key="frame" x="16" y="11" width="351" height="124"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Content Warning" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhS-ig-6Fu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="351" height="18"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zMS-88-DcM">
|
||||
<rect key="frame" x="0.0" y="26" width="351" height="40"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" ambiguous="YES" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8eA-yd-rBp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="310.5" height="32"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D2X-9O-iQw">
|
||||
<rect key="frame" x="326.5" y="0.0" width="24.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="D2X-9O-iQw" firstAttribute="leading" secondItem="8eA-yd-rBp" secondAttribute="trailing" constant="16" id="6Ux-ee-J5h"/>
|
||||
<constraint firstAttribute="trailing" secondItem="D2X-9O-iQw" secondAttribute="trailing" id="IRH-mM-HSs"/>
|
||||
<constraint firstItem="8eA-yd-rBp" firstAttribute="leading" secondItem="zMS-88-DcM" secondAttribute="leading" id="StS-F9-9B3"/>
|
||||
<constraint firstItem="8eA-yd-rBp" firstAttribute="top" secondItem="zMS-88-DcM" secondAttribute="top" id="Uuq-g5-n0A"/>
|
||||
<constraint firstItem="D2X-9O-iQw" firstAttribute="top" secondItem="zMS-88-DcM" secondAttribute="top" id="lWB-6Z-nbG"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8eA-yd-rBp" secondAttribute="bottom" id="zCK-s5-4Zo"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="csc-gx-KVg">
|
||||
<rect key="frame" x="0.0" y="74" width="351" height="50"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="htC-hf-vJ4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="352" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="lxT-O2-afE"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="htC-hf-vJ4" firstAttribute="leading" secondItem="csc-gx-KVg" secondAttribute="leading" id="c0s-O9-XKa"/>
|
||||
<constraint firstItem="htC-hf-vJ4" firstAttribute="top" secondItem="csc-gx-KVg" secondAttribute="top" id="lcl-RN-qHw"/>
|
||||
<constraint firstAttribute="bottom" secondItem="htC-hf-vJ4" secondAttribute="bottom" id="oHX-Qh-bmI"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="csc-gx-KVg" secondAttribute="trailing" id="AcZ-yc-8Zh"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="gaD-3B-qO1" secondAttribute="bottomMargin" constant="8" id="4Hz-ax-JI6"/>
|
||||
<constraint firstItem="gaD-3B-qO1" firstAttribute="leading" secondItem="KVi-jA-AET" secondAttribute="leadingMargin" id="KRA-Q8-klX"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gaD-3B-qO1" secondAttribute="trailingMargin" constant="8" id="iGc-c4-n9y"/>
|
||||
<constraint firstItem="gaD-3B-qO1" firstAttribute="top" secondItem="KVi-jA-AET" secondAttribute="topMargin" id="rVE-Jo-6zG"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="attachmentsStackView" destination="htC-hf-vJ4" id="kEX-m7-LuE"/>
|
||||
<outlet property="attachmentsStackViewContainer" destination="csc-gx-KVg" id="rIM-pj-TFX"/>
|
||||
<outlet property="contentLabel" destination="8eA-yd-rBp" id="Uy0-8G-WbU"/>
|
||||
<outlet property="contentWarningLabel" destination="VhS-ig-6Fu" id="jIU-vr-OsY"/>
|
||||
<outlet property="lastModifiedLabel" destination="D2X-9O-iQw" id="dx7-0E-RuM"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-388" y="184.85757121439281"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
Loading…
Reference in New Issue