Compare commits
6 Commits
10239d14c9
...
d05275020f
Author | SHA1 | Date |
---|---|---|
Shadowfacts | d05275020f | |
Shadowfacts | c420c236d9 | |
Shadowfacts | d5433e9b91 | |
Shadowfacts | cbbe9ec11f | |
Shadowfacts | 0e06d47687 | |
Shadowfacts | c907b7257a |
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
|||
# Changelog
|
||||
|
||||
## 2022.1 (41)
|
||||
Features/Improvements:
|
||||
- Rewrite profile screens to use new timeline implementation
|
||||
- Disable Infinite Scrolling preference (Preferences -> Digital Wellness) now applies to profiles
|
||||
- Improve behavior when switching tabs on profiles
|
||||
- Improve pointer interaction on timeline status cells
|
||||
|
||||
Bugfixes:
|
||||
- Fix crash when loading images in certain circumstances
|
||||
- Fix gallery dismissal leaving status bar hidden and breaking future gallery dismisses
|
||||
- Fix timeline scroll position changing after dismissing gallery
|
||||
- Fix images flickering when switching back to the Home tab
|
||||
- Fix crash reporter being dismissed when sending email is cancelled
|
||||
- Fix crash when long pressing Send Report button in crash reporter on iPad
|
||||
- Fix Live Text controls not hiding when other gallery controls are hidden
|
||||
- Fix replies appearing multiple times in Drafts list
|
||||
- Fix crash when displaying blur hash images on Pleroma
|
||||
|
||||
## 2022.1 (40)
|
||||
Bugfixes:
|
||||
- Fix selecting reblogged statuses in the timeline
|
||||
|
|
|
@ -2217,7 +2217,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
|
@ -2246,7 +2246,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
|
@ -2356,7 +2356,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
@ -2383,7 +2383,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
|
|
@ -147,19 +147,20 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
}
|
||||
|
||||
@discardableResult
|
||||
private func upsert(account: Account) -> AccountMO {
|
||||
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
||||
private func upsert(account: Account, in context: NSManagedObjectContext) -> AccountMO {
|
||||
if let accountMO = self.account(for: account.id, in: context) {
|
||||
accountMO.updateFrom(apiAccount: account, container: self)
|
||||
return accountMO
|
||||
} else {
|
||||
return AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
||||
return AccountMO(apiAccount: account, container: self, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
|
||||
backgroundContext.perform {
|
||||
let accountMO = self.upsert(account: account)
|
||||
self.save(context: self.backgroundContext)
|
||||
func addOrUpdate(account: Account, in context: NSManagedObjectContext? = nil, completion: ((AccountMO) -> Void)? = nil) {
|
||||
let context = context ?? backgroundContext
|
||||
context.perform {
|
||||
let accountMO = self.upsert(account: account, in: context)
|
||||
self.save(context: context)
|
||||
completion?(accountMO)
|
||||
self.accountSubject.send(account.id)
|
||||
}
|
||||
|
@ -199,7 +200,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
|
||||
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
|
||||
backgroundContext.perform {
|
||||
accounts.forEach { self.upsert(account: $0) }
|
||||
accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) }
|
||||
self.save(context: self.backgroundContext)
|
||||
completion?()
|
||||
accounts.forEach { self.accountSubject.send($0.id) }
|
||||
|
@ -213,7 +214,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
// since the status has the same account as the notification
|
||||
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
|
||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
||||
accounts.forEach { self.upsert(account: $0) }
|
||||
accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) }
|
||||
self.save(context: self.backgroundContext)
|
||||
completion?()
|
||||
statuses.forEach { self.statusSubject.send($0.id) }
|
||||
|
@ -227,7 +228,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
|||
var updatedStatuses = [String]()
|
||||
|
||||
block(self.backgroundContext, { (accounts) in
|
||||
accounts.forEach { self.upsert(account: $0) }
|
||||
accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) }
|
||||
updatedAccounts.append(contentsOf: accounts.map { $0.id })
|
||||
}, { (statuses) in
|
||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<array>
|
||||
<string>counter\.social</string>
|
||||
<string>gab\..+</string>
|
||||
</array>
|
||||
</plist>
|
||||
|
|
|
@ -18,6 +18,12 @@ fileprivate let instanceCell = "instanceCell"
|
|||
|
||||
class InstanceSelectorTableViewController: UITableViewController {
|
||||
|
||||
static var blocks: [NSRegularExpression] = {
|
||||
guard let path = Bundle.main.path(forResource: "DomainBlocks", ofType: "plist"),
|
||||
let array = NSArray(contentsOfFile: path) as? [String] else { return [] }
|
||||
return array.compactMap { try? NSRegularExpression(pattern: $0, options: .caseInsensitive) }
|
||||
}()
|
||||
|
||||
weak var delegate: InstanceSelectorTableViewControllerDelegate?
|
||||
|
||||
var dataSource: DataSource!
|
||||
|
@ -100,7 +106,7 @@ class InstanceSelectorTableViewController: UITableViewController {
|
|||
loadRecommendedInstances()
|
||||
}
|
||||
|
||||
private func parseURLComponents(input: String) -> URLComponents {
|
||||
private func parseURLComponents(input: String) -> URLComponents? {
|
||||
// we can't just use the URLComponents(string:) initializer, because when given just a domain (w/o protocol), it interprets it as the path
|
||||
var input = input
|
||||
var components = URLComponents()
|
||||
|
@ -125,13 +131,24 @@ class InstanceSelectorTableViewController: UITableViewController {
|
|||
components.port = Int(parts.last!)
|
||||
}
|
||||
components.host = input
|
||||
if Self.blocks.contains(where: { $0.numberOfMatches(in: input, range: NSRange(location: 0, length: input.utf16.count)) > 0 }) {
|
||||
return nil
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
private func updateSpecificInstance(domain: String) {
|
||||
activityIndicator.startAnimating()
|
||||
|
||||
let components = parseURLComponents(input: domain)
|
||||
guard let components = parseURLComponents(input: domain) else {
|
||||
var snapshot = dataSource.snapshot()
|
||||
if snapshot.indexOfSection(.selected) != nil {
|
||||
snapshot.deleteSections([.selected])
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
activityIndicator.stopAnimating()
|
||||
return
|
||||
}
|
||||
let url = components.url!
|
||||
|
||||
let client = Client(baseURL: url, session: .appDefault)
|
||||
|
|
|
@ -17,12 +17,6 @@ protocol OnboardingViewControllerDelegate {
|
|||
|
||||
class OnboardingViewController: UINavigationController {
|
||||
|
||||
static var blocks: [NSRegularExpression] = {
|
||||
guard let path = Bundle.main.path(forResource: "DomainBlocks", ofType: "plist"),
|
||||
let array = NSArray(contentsOfFile: path) as? [String] else { return [] }
|
||||
return array.compactMap { try? NSRegularExpression(pattern: $0, options: .caseInsensitive) }
|
||||
}()
|
||||
|
||||
var onboardingDelegate: OnboardingViewControllerDelegate?
|
||||
|
||||
var instanceSelector = InstanceSelectorTableViewController()
|
||||
|
|
|
@ -29,9 +29,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
view as! UICollectionView
|
||||
}
|
||||
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||
|
||||
private(set) var headerCell: ProfileHeaderCollectionViewCell?
|
||||
|
||||
private var state: State = .unloaded
|
||||
|
||||
init(accountID: String?, kind: Kind, owner: ProfileViewController) {
|
||||
self.accountID = accountID
|
||||
self.kind = kind
|
||||
|
@ -41,16 +42,6 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
|
||||
self.controller = TimelineLikeController(delegate: self)
|
||||
|
||||
mastodonController.persistentContainer.accountSubject
|
||||
.receive(on: DispatchQueue.main)
|
||||
.filter { [unowned self] in $0 == self.accountID }
|
||||
.sink { [unowned self] id in
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.reconfigureItems([.header(id)])
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh Profile"))
|
||||
}
|
||||
|
||||
|
@ -97,7 +88,25 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
mastodonController.persistentContainer.accountSubject
|
||||
.receive(on: DispatchQueue.main)
|
||||
.filter { [unowned self] in $0 == self.accountID }
|
||||
.sink { [unowned self] id in
|
||||
switch state {
|
||||
case .unloaded:
|
||||
Task {
|
||||
await load()
|
||||
}
|
||||
case .loading:
|
||||
break
|
||||
case .loaded, .addedHeader:
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.reconfigureItems([.header(id)])
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
|
@ -160,20 +169,26 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
}
|
||||
|
||||
private func load() async {
|
||||
guard accountID != nil,
|
||||
await controller.state == .notLoadedInitial,
|
||||
isViewLoaded else {
|
||||
guard isViewLoaded,
|
||||
let accountID,
|
||||
case .unloaded = state,
|
||||
mastodonController.persistentContainer.account(for: accountID) != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
state = .loading
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.header, .pinned, .statuses])
|
||||
snapshot.appendItems([.header(accountID)], toSection: .header)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
print("added header item")
|
||||
|
||||
state = .addedHeader
|
||||
|
||||
await controller.loadInitial()
|
||||
await tryLoadPinned()
|
||||
|
||||
state = .loaded
|
||||
}
|
||||
|
||||
private func tryLoadPinned() async {
|
||||
|
@ -223,6 +238,15 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
|
||||
}
|
||||
|
||||
extension ProfileStatusesViewController {
|
||||
enum State {
|
||||
case unloaded
|
||||
case loading
|
||||
case addedHeader
|
||||
case loaded
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileStatusesViewController {
|
||||
enum Kind {
|
||||
case statuses, withReplies, onlyMedia
|
||||
|
@ -436,7 +460,7 @@ extension ProfileStatusesViewController: StatusCollectionViewCellDelegate {
|
|||
if let indexPath = collectionView.indexPath(for: cell) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.reconfigureItems([dataSource.itemIdentifier(for: indexPath)!])
|
||||
dataSource.apply(snapshot, animatingDifferences: false, completion: completion)
|
||||
dataSource.apply(snapshot, animatingDifferences: animated, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ class ProfileViewController: UIPageViewController {
|
|||
let req = Client.getAccount(id: accountID)
|
||||
let (account, _) = try await mastodonController.run(req)
|
||||
let mo = await withCheckedContinuation { continuation in
|
||||
mastodonController.persistentContainer.addOrUpdate(account: account) { (mo) in
|
||||
mastodonController.persistentContainer.addOrUpdate(account: account, in: mastodonController.persistentContainer.viewContext) { (mo) in
|
||||
continuation.resume(returning: mo)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,7 +261,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
|
||||
mainContainerTopToReblogLabelConstraint = mainContainer.topAnchor.constraint(equalTo: reblogLabel.bottomAnchor, constant: 4)
|
||||
mainContainerTopToSelfConstraint = mainContainer.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8)
|
||||
mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor)
|
||||
mainContainerBottomToActionsConstraint = mainContainer.bottomAnchor.constraint(equalTo: actionsContainer.topAnchor, constant: -4)
|
||||
mainContainerBottomToSelfConstraint = mainContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6)
|
||||
|
||||
let metaIndicatorsBottomConstraint = metaIndicatorsView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -6)
|
||||
|
|
Loading…
Reference in New Issue