Compare commits

...

4 Commits

7 changed files with 93 additions and 22 deletions

View File

@ -1,5 +1,38 @@
# Changelog # Changelog
## 2020.1 (9)
The marquee feature of this build is the new and improved Compose screen. It's been rewritten to use SwiftUI, is significantly more resilient to data loss, and now shows the toolbar when the main text field is not focused. It also turns out Apple is surprise-releasing iOS 14 very soon (or possibly already has, depending when you're reading this). For those who were not already on the beta train, iOS 14 brings a number of new features including a sidebar on iPadOS and lots and lots of context menus (a home screen widget is coming Soon™).
Known Issues:
- Pasting images to create attachments when composing a post is not currently supported due to an iOS bug (#109)
- Full-size previews do not display in context menus for attachments on the Compose screen due to an iOS issue (#110)
Features/Improvements:
- Rewrite Compose screen using SwiftUI
- Prevent draft posts being lost if the app crahes or is killed by the system while composing
- Show toolbar while post content is not being edited
- Save post visibility in drafts
- Move Draw Something action out of the context menu
- iOS 14: Use context menus for setting post visibility
- Show BlurHash previews for attachments on Mastodon
- Add Expand All Content Warnings preference (Preferences -> Behavior)
- Add Collapse Long Posts preference (Preferences -> Behavior)
- Improve image gallery opening animation
- Use fade in/out animations for opening/closing gallery and attachment picker when the Reduce Motion system setting is enabled
- iOS 14: Also requires the "Prefer Cross Fade" setting be enabled
- Slightly reduce default status font sizes
- Add "Direct Message" context menu action to Compose button on profile screen
- Allow viewing attachments and navigating through posts/accounts on instance public timelines
Bugfixes:
- Fix errors when uploading attachments not displaying
- Fix attachments not posting in the correct, user-specified order
- Fix accounts displaying with outdated information (avatars, display names, etc.)
- Fix Compose not showing button on profile screen
- Fix navigation title not being set on profile screen
- Fix follow notifications not showing names for users without display names set
- iPadOS 14: Fix crash when resizing app in split view mode
## 2020.1 (8) ## 2020.1 (8)
This is just an emergency build to fix crashes on iOS 13 when selecting attachments. The changelog of the previous build is included below. This is just an emergency build to fix crashes on iOS 13 when selecting attachments. The changelog of the previous build is included below.

View File

@ -2229,7 +2229,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 = 8; CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4; IPHONEOS_DEPLOYMENT_TARGET = 13.4;
@ -2258,7 +2258,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 = 8; CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4; IPHONEOS_DEPLOYMENT_TARGET = 13.4;

View File

@ -84,23 +84,28 @@ class MastodonController {
} }
} }
func getOwnAccount(completion: ((Account) -> Void)? = nil) { func getOwnAccount(completion: ((Result<Account, Client.Error>) -> Void)? = nil) {
if account != nil { if account != nil {
completion?(account) completion?(.success(account))
} else { } else {
let request = Client.getSelfAccount() let request = Client.getSelfAccount()
run(request) { response in run(request) { response in
guard case let .success(account, _) = response else { fatalError() } switch response {
self.account = account case let .failure(error):
self.persistentContainer.backgroundContext.perform { completion?(.failure(error))
if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
accountMO.updateFrom(apiAccount: account, container: self.persistentContainer) case let .success(account, _):
} else { self.account = account
// the first time the user's account is added to the store, self.persistentContainer.backgroundContext.perform {
// increment its reference count so that it's never removed if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
self.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
} else {
// the first time the user's account is added to the store,
// increment its reference count so that it's never removed
self.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true)
}
completion?(.success(account))
} }
completion?(account)
} }
} }
} }

View File

@ -119,10 +119,29 @@ extension MastodonController {
func createDraft(inReplyToID: String? = nil, mentioningAcct: String? = nil) -> Draft { func createDraft(inReplyToID: String? = nil, mentioningAcct: String? = nil) -> Draft {
var acctsToMention = [String]() var acctsToMention = [String]()
var visibility = Preferences.shared.defaultPostVisibility
var contentWarning = ""
if let inReplyToID = inReplyToID, if let inReplyToID = inReplyToID,
let inReplyTo = persistentContainer.status(for: inReplyToID) { let inReplyTo = persistentContainer.status(for: inReplyToID) {
acctsToMention.append(inReplyTo.account.acct) acctsToMention.append(inReplyTo.account.acct)
acctsToMention.append(contentsOf: inReplyTo.mentions.map(\.acct)) acctsToMention.append(contentsOf: inReplyTo.mentions.map(\.acct))
visibility = inReplyTo.visibility
if !inReplyTo.spoilerText.isEmpty {
switch Preferences.shared.contentWarningCopyMode {
case .doNotCopy:
break
case .asIs:
contentWarning = inReplyTo.spoilerText
case .prependRe:
if inReplyTo.spoilerText.lowercased().starts(with: "re:") {
contentWarning = inReplyTo.spoilerText
} else {
contentWarning = "re: \(inReplyTo.spoilerText)"
}
}
}
} }
if let mentioningAcct = mentioningAcct { if let mentioningAcct = mentioningAcct {
acctsToMention.append(mentioningAcct) acctsToMention.append(mentioningAcct)
@ -136,6 +155,9 @@ extension MastodonController {
draft.inReplyToID = inReplyToID draft.inReplyToID = inReplyToID
draft.text = acctsToMention.map { "@\($0) " }.joined() draft.text = acctsToMention.map { "@\($0) " }.joined()
draft.initialText = draft.text draft.initialText = draft.text
draft.visibility = visibility
draft.contentWarning = contentWarning
draft.contentWarningEnabled = !contentWarning.isEmpty
return draft return draft
} }

View File

@ -70,13 +70,21 @@ extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate
let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken) let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
mastodonController.accountInfo = tempAccountInfo mastodonController.accountInfo = tempAccountInfo
mastodonController.getOwnAccount { (account) in mastodonController.getOwnAccount { (result) in
DispatchQueue.main.async { DispatchQueue.main.async {
// this needs to happen on the main thread because it publishes a new value for the ObservableObject switch result {
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken) case let .failure(error):
mastodonController.accountInfo = accountInfo let alert = UIAlertController(title: "Unable to Verify Credentials", message: "Your account could not be fetched at this time: \(error.localizedDescription)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo) self.present(alert, animated: true)
case let .success(account):
// this needs to happen on the main thread because it publishes a new value for the ObservableObject
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
mastodonController.accountInfo = accountInfo
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
}
} }
} }
} }

View File

@ -36,7 +36,8 @@ struct LocalAccountAvatarView: View {
func loadImage() { func loadImage() {
let controller = MastodonController.getForAccount(localAccountInfo) let controller = MastodonController.getForAccount(localAccountInfo)
controller.getOwnAccount { (account) in controller.getOwnAccount { (result) in
guard case let .success(account) = result else { return }
_ = ImageCache.avatars.get(account.avatar) { (data) in _ = ImageCache.avatars.get(account.avatar) { (data) in
if let data = data, let image = UIImage(data: data) { if let data = data, let image = UIImage(data: data) {
DispatchQueue.main.async { DispatchQueue.main.async {

View File

@ -16,7 +16,9 @@ class MyProfileViewController: ProfileViewController {
title = "My Profile" title = "My Profile"
tabBarItem.image = UIImage(systemName: "person.fill") tabBarItem.image = UIImage(systemName: "person.fill")
mastodonController.getOwnAccount { (account) in mastodonController.getOwnAccount { (result) in
guard case let .success(account) = result else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
self.accountID = account.id self.accountID = account.id
} }