Compare commits
4 Commits
76fc73de95
...
f23d3dfa3f
Author | SHA1 | Date |
---|---|---|
Shadowfacts | f23d3dfa3f | |
Shadowfacts | 23f9e200dc | |
Shadowfacts | 366834e2e4 | |
Shadowfacts | d409d26478 |
|
@ -1,5 +1,12 @@
|
|||
# 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.
|
||||
|
||||
|
|
|
@ -2195,7 +2195,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 46;
|
||||
CURRENT_PROJECT_VERSION = 48;
|
||||
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 = 46;
|
||||
CURRENT_PROJECT_VERSION = 48;
|
||||
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 = 46;
|
||||
CURRENT_PROJECT_VERSION = 48;
|
||||
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 = 46;
|
||||
CURRENT_PROJECT_VERSION = 48;
|
||||
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 = 46;
|
||||
CURRENT_PROJECT_VERSION = 48;
|
||||
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 = 46;
|
||||
CURRENT_PROJECT_VERSION = 48;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
|
|
@ -16,7 +16,7 @@ protocol ComposeHostingControllerDelegate: AnyObject {
|
|||
func dismissCompose(mode: ComposeUIState.DismissMode) -> Bool
|
||||
}
|
||||
|
||||
class ComposeHostingController: UIHostingController<ComposeView>, DuckableViewController {
|
||||
class ComposeHostingController: UIHostingController<ComposeHostingController.Wrapper>, DuckableViewController {
|
||||
|
||||
weak var delegate: ComposeHostingControllerDelegate?
|
||||
weak var duckableDelegate: DuckableViewControllerDelegate?
|
||||
|
@ -36,11 +36,11 @@ class ComposeHostingController: UIHostingController<ComposeView>, DuckableViewCo
|
|||
|
||||
self.uiState = ComposeUIState(draft: realDraft)
|
||||
|
||||
let compose = ComposeView(
|
||||
let wrapper = Wrapper(
|
||||
mastodonController: mastodonController,
|
||||
uiState: uiState
|
||||
)
|
||||
super.init(rootView: compose)
|
||||
super.init(rootView: wrapper)
|
||||
|
||||
self.uiState.delegate = self
|
||||
|
||||
|
@ -129,6 +129,23 @@ class ComposeHostingController: UIHostingController<ComposeView>, DuckableViewCo
|
|||
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
|
|
|
@ -42,11 +42,9 @@ import Combine
|
|||
}
|
||||
|
||||
struct ComposeView: View {
|
||||
@ObservedObject var mastodonController: MastodonController
|
||||
@ObservedObject var uiState: ComposeUIState
|
||||
var draft: Draft {
|
||||
uiState.draft
|
||||
}
|
||||
@EnvironmentObject var mastodonController: MastodonController
|
||||
@EnvironmentObject var uiState: ComposeUIState
|
||||
@EnvironmentObject var draft: Draft
|
||||
|
||||
@State private var globalFrameOutsideList: CGRect = .zero
|
||||
@State private var contentWarningBecomeFirstResponder = false
|
||||
|
@ -62,11 +60,6 @@ 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
|
||||
|
@ -84,12 +77,6 @@ struct ComposeView: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
bodyWithoutEnvironment
|
||||
.environmentObject(uiState)
|
||||
.environmentObject(mastodonController)
|
||||
}
|
||||
|
||||
private var bodyWithoutEnvironment: some View {
|
||||
ZStack(alignment: .top) {
|
||||
mainList
|
||||
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
||||
|
@ -173,7 +160,7 @@ struct ComposeView: View {
|
|||
.listRowInsets(EdgeInsets(top: draft.inReplyToID == nil ? 8 : 4, leading: 8, bottom: 4, trailing: 8))
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
if draft.contentWarningEnabled {
|
||||
if uiState.draft.contentWarningEnabled {
|
||||
ComposeEmojiTextField(
|
||||
text: $uiState.draft.contentWarning,
|
||||
placeholder: "Write your warning here",
|
||||
|
|
|
@ -184,39 +184,44 @@ 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 firstVisible = visible.first(where: { $0.section == statusesSection }),
|
||||
let lastVisible = visible.last(where: { $0.section == statusesSection }) else {
|
||||
let rawCenterVisible = collectionView.indexPathForItem(at: midPoint),
|
||||
let centerVisible = visible.first(where: { $0.section == statusesSection && $0 >= rawCenterVisible }) else {
|
||||
return nil
|
||||
}
|
||||
let allItems = snapshot.itemIdentifiers(inSection: .statuses)
|
||||
|
||||
let startIndex = max(0, firstVisible.row - 20)
|
||||
let endIndex = min(allItems.count - 1, lastVisible.row + 20)
|
||||
let startIndex = max(0, centerVisible.row - 20)
|
||||
let endIndex = min(allItems.count - 1, centerVisible.row + 20)
|
||||
|
||||
let firstVisibleItem: Item
|
||||
let centerVisibleItem: 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 <= firstVisible.row {
|
||||
if gapIndex <= centerVisible.row {
|
||||
items = allItems[(gapIndex + 1)...endIndex]
|
||||
if gapIndex == firstVisible.row {
|
||||
firstVisibleItem = allItems.first!
|
||||
if gapIndex == centerVisible.row {
|
||||
centerVisibleItem = allItems.first!
|
||||
} else {
|
||||
assert(items.indices.contains(firstVisible.row))
|
||||
firstVisibleItem = allItems[firstVisible.row]
|
||||
assert(items.indices.contains(centerVisible.row))
|
||||
centerVisibleItem = allItems[centerVisible.row]
|
||||
}
|
||||
} else {
|
||||
items = allItems[startIndex..<gapIndex]
|
||||
firstVisibleItem = allItems[firstVisible.row]
|
||||
centerVisibleItem = allItems[centerVisible.row]
|
||||
}
|
||||
} else {
|
||||
firstVisibleItem = allItems[firstVisible.row]
|
||||
centerVisibleItem = allItems[centerVisible.row]
|
||||
}
|
||||
let ids = items.map {
|
||||
if case .status(id: let id, state: _) = $0 {
|
||||
|
@ -225,18 +230,18 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
fatalError()
|
||||
}
|
||||
}
|
||||
let firstVisibleID: String
|
||||
if case .status(id: let id, state: _) = firstVisibleItem {
|
||||
firstVisibleID = id
|
||||
let centerVisibleID: String
|
||||
if case .status(id: let id, state: _) = centerVisibleItem {
|
||||
centerVisibleID = id
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
stateRestorationLogger.debug("TimelineViewController: creating state restoration activity with topID \(firstVisibleID)")
|
||||
stateRestorationLogger.debug("TimelineViewController: creating state restoration activity with topID \(centerVisibleID)")
|
||||
|
||||
let activity = UserActivityManager.showTimelineActivity(timeline: timeline, accountID: currentAccountID)!
|
||||
activity.addUserInfoEntries(from: [
|
||||
"statusIDs": ids,
|
||||
"topID": firstVisibleID,
|
||||
"centerID": centerVisibleID,
|
||||
])
|
||||
activity.isEligibleForPrediction = false
|
||||
return activity
|
||||
|
@ -260,8 +265,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 topID = activity.userInfo?["topID"] as? String,
|
||||
let index = statusIDs.firstIndex(of: topID),
|
||||
if let centerID = activity.userInfo?["centerID"] as? String ?? activity.userInfo?["topID"] as? String,
|
||||
let index = statusIDs.firstIndex(of: centerID),
|
||||
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
|
||||
|
@ -270,15 +275,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
count += 1
|
||||
let origOffset = self.collectionView.contentOffset
|
||||
self.collectionView.layoutIfNeeded()
|
||||
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: false)
|
||||
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
|
||||
let newOffset = self.collectionView.contentOffset
|
||||
if abs(origOffset.y - newOffset.y) <= 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
stateRestorationLogger.fault("TimelineViewController: restored statuses with top ID \(topID)")
|
||||
stateRestorationLogger.fault("TimelineViewController: restored statuses with center ID \(centerID)")
|
||||
} else {
|
||||
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find top ID")
|
||||
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find center ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue