Compare commits

..

No commits in common. "f23d3dfa3fb77423fb574a02363be0e839abfbc7" and "76fc73de957b9199179656bb88e84b1568090de1" have entirely different histories.

5 changed files with 48 additions and 64 deletions

View File

@ -1,12 +1,5 @@
# Changelog
## 2022.1 (47)
This build is a hotfix for the CW button in the Compose screen not working. The previous build's changelog is attached below.
Bugfixes:
- Fix pressing CW button in Compose not showing the content warning field
- Tweak timeline state restoration to try and maintain the scroll position of the middle item on screen, rather than the top one
## 2022.1 (46)
The headlining feature is state restoration and timeline gaps! When you re-open the app after it's been closed for a while, it will remember your position in the timeline and allow you to keep reading from there. It will also let you jump all the way to the present.

View File

@ -2195,7 +2195,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2263,7 +2263,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2413,7 +2413,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2442,7 +2442,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2552,7 +2552,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2579,7 +2579,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 48;
CURRENT_PROJECT_VERSION = 46;
DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;

View File

@ -16,7 +16,7 @@ protocol ComposeHostingControllerDelegate: AnyObject {
func dismissCompose(mode: ComposeUIState.DismissMode) -> Bool
}
class ComposeHostingController: UIHostingController<ComposeHostingController.Wrapper>, DuckableViewController {
class ComposeHostingController: UIHostingController<ComposeView>, DuckableViewController {
weak var delegate: ComposeHostingControllerDelegate?
weak var duckableDelegate: DuckableViewControllerDelegate?
@ -36,11 +36,11 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
self.uiState = ComposeUIState(draft: realDraft)
let wrapper = Wrapper(
let compose = ComposeView(
mastodonController: mastodonController,
uiState: uiState
)
super.init(rootView: wrapper)
super.init(rootView: compose)
self.uiState.delegate = self
@ -129,23 +129,6 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Wra
}
extension ComposeHostingController {
struct Wrapper: View {
let mastodonController: MastodonController
@ObservedObject var uiState: ComposeUIState
var draft: Draft {
uiState.draft
}
var body: some View {
ComposeView()
.environmentObject(mastodonController)
.environmentObject(uiState)
.environmentObject(draft)
}
}
}
extension ComposeHostingController: ComposeUIStateDelegate {
var assetPickerDelegate: AssetPickerViewControllerDelegate? { self }

View File

@ -42,9 +42,11 @@ import Combine
}
struct ComposeView: View {
@EnvironmentObject var mastodonController: MastodonController
@EnvironmentObject var uiState: ComposeUIState
@EnvironmentObject var draft: Draft
@ObservedObject var mastodonController: MastodonController
@ObservedObject var uiState: ComposeUIState
var draft: Draft {
uiState.draft
}
@State private var globalFrameOutsideList: CGRect = .zero
@State private var contentWarningBecomeFirstResponder = false
@ -60,6 +62,11 @@ struct ComposeView: View {
private let stackPadding: CGFloat = 8
init(mastodonController: MastodonController, uiState: ComposeUIState) {
self.mastodonController = mastodonController
self.uiState = uiState
}
private var charactersRemaining: Int {
let limit = mastodonController.instanceFeatures.maxStatusChars
let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0
@ -77,6 +84,12 @@ struct ComposeView: View {
}
var body: some View {
bodyWithoutEnvironment
.environmentObject(uiState)
.environmentObject(mastodonController)
}
private var bodyWithoutEnvironment: some View {
ZStack(alignment: .top) {
mainList
.scrollDismissesKeyboardInteractivelyIfAvailable()
@ -160,7 +173,7 @@ struct ComposeView: View {
.listRowInsets(EdgeInsets(top: draft.inReplyToID == nil ? 8 : 4, leading: 8, bottom: 4, trailing: 8))
.listRowSeparator(.hidden)
if uiState.draft.contentWarningEnabled {
if draft.contentWarningEnabled {
ComposeEmojiTextField(
text: $uiState.draft.contentWarning,
placeholder: "Write your warning here",

View File

@ -184,44 +184,39 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
}
func stateRestorationActivity() -> NSUserActivity? {
guard isViewLoaded else {
return nil
}
let visible = collectionView.indexPathsForVisibleItems.sorted()
let snapshot = dataSource.snapshot()
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let midPoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
guard let currentAccountID = mastodonController.accountInfo?.id,
!visible.isEmpty,
let statusesSection = snapshot.sectionIdentifiers.firstIndex(of: .statuses),
let rawCenterVisible = collectionView.indexPathForItem(at: midPoint),
let centerVisible = visible.first(where: { $0.section == statusesSection && $0 >= rawCenterVisible }) else {
let firstVisible = visible.first(where: { $0.section == statusesSection }),
let lastVisible = visible.last(where: { $0.section == statusesSection }) else {
return nil
}
let allItems = snapshot.itemIdentifiers(inSection: .statuses)
let startIndex = max(0, centerVisible.row - 20)
let endIndex = min(allItems.count - 1, centerVisible.row + 20)
let startIndex = max(0, firstVisible.row - 20)
let endIndex = min(allItems.count - 1, lastVisible.row + 20)
let centerVisibleItem: Item
let firstVisibleItem: Item
var items = allItems[startIndex...endIndex]
if let gapIndex = items.firstIndex(of: .gap) {
// if the gap is above the top visible item, we take everything below the gap
// otherwise, we take everything above the gap
if gapIndex <= centerVisible.row {
if gapIndex <= firstVisible.row {
items = allItems[(gapIndex + 1)...endIndex]
if gapIndex == centerVisible.row {
centerVisibleItem = allItems.first!
if gapIndex == firstVisible.row {
firstVisibleItem = allItems.first!
} else {
assert(items.indices.contains(centerVisible.row))
centerVisibleItem = allItems[centerVisible.row]
assert(items.indices.contains(firstVisible.row))
firstVisibleItem = allItems[firstVisible.row]
}
} else {
items = allItems[startIndex..<gapIndex]
centerVisibleItem = allItems[centerVisible.row]
firstVisibleItem = allItems[firstVisible.row]
}
} else {
centerVisibleItem = allItems[centerVisible.row]
firstVisibleItem = allItems[firstVisible.row]
}
let ids = items.map {
if case .status(id: let id, state: _) = $0 {
@ -230,18 +225,18 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
fatalError()
}
}
let centerVisibleID: String
if case .status(id: let id, state: _) = centerVisibleItem {
centerVisibleID = id
let firstVisibleID: String
if case .status(id: let id, state: _) = firstVisibleItem {
firstVisibleID = id
} else {
fatalError()
}
stateRestorationLogger.debug("TimelineViewController: creating state restoration activity with topID \(centerVisibleID)")
stateRestorationLogger.debug("TimelineViewController: creating state restoration activity with topID \(firstVisibleID)")
let activity = UserActivityManager.showTimelineActivity(timeline: timeline, accountID: currentAccountID)!
activity.addUserInfoEntries(from: [
"statusIDs": ids,
"centerID": centerVisibleID,
"topID": firstVisibleID,
])
activity.isEligibleForPrediction = false
return activity
@ -265,8 +260,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
let items = statusIDs.map { Item.status(id: $0, state: .unknown) }
snapshot.appendItems(items, toSection: .statuses)
dataSource.apply(snapshot, animatingDifferences: false) {
if let centerID = activity.userInfo?["centerID"] as? String ?? activity.userInfo?["topID"] as? String,
let index = statusIDs.firstIndex(of: centerID),
if let topID = activity.userInfo?["topID"] as? String,
let index = statusIDs.firstIndex(of: topID),
let indexPath = self.dataSource.indexPath(for: items[index]) {
// it sometimes takes multiple attempts to convert on the right scroll position
// since we're dealing with a bunch of unmeasured cells, so just try a few times in a loop
@ -275,15 +270,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
count += 1
let origOffset = self.collectionView.contentOffset
self.collectionView.layoutIfNeeded()
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: false)
let newOffset = self.collectionView.contentOffset
if abs(origOffset.y - newOffset.y) <= 1 {
break
}
}
stateRestorationLogger.fault("TimelineViewController: restored statuses with center ID \(centerID)")
stateRestorationLogger.fault("TimelineViewController: restored statuses with top ID \(topID)")
} else {
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find center ID")
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find top ID")
}
}
}