Add sort control for macOS

This commit is contained in:
Maurice Parker 2020-07-12 19:43:25 -05:00
parent 3a67f2cd8e
commit 17e1247ff0
7 changed files with 109 additions and 34 deletions

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17132"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -185,7 +186,7 @@
<toolbarItem implicitItemIdentifier="ACB5604B-4543-4985-BA1A-54ADA9DF5845" label="Clean UP" paletteLabel="Clean Up" toolTip="Clean Up" image="cleanUp" sizingBehavior="auto" id="SsT-iS-pKE" customClass="RSToolbarItem" customModule="RSCore"> <toolbarItem implicitItemIdentifier="ACB5604B-4543-4985-BA1A-54ADA9DF5845" label="Clean UP" paletteLabel="Clean Up" toolTip="Clean Up" image="cleanUp" sizingBehavior="auto" id="SsT-iS-pKE" customClass="RSToolbarItem" customModule="RSCore">
<button key="view" verticalHuggingPriority="750" id="9At-yP-WNY"> <button key="view" verticalHuggingPriority="750" id="9At-yP-WNY">
<rect key="frame" x="7" y="14" width="42" height="25"/> <rect key="frame" x="7" y="14" width="42" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="cleanUp" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Zwg-74-ZkZ"> <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="cleanUp" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Zwg-74-ZkZ">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
@ -318,16 +319,16 @@
<rect key="frame" x="0.0" y="0.0" width="166" height="283"/> <rect key="frame" x="0.0" y="0.0" width="166" height="283"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="2eU-Wz-F9g"> <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="2eU-Wz-F9g">
<rect key="frame" x="0.0" y="0.0" width="166" height="283"/> <rect key="frame" x="0.0" y="0.0" width="166" height="283"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="26" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="23" outlineTableColumn="ih9-mJ-EA7" id="cnV-kg-Dn2" customClass="SidebarOutlineView" customModule="NetNewsWire" customModuleProvider="target"> <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="26" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="23" outlineTableColumn="ih9-mJ-EA7" id="cnV-kg-Dn2" customClass="SidebarOutlineView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="167" height="283"/> <rect key="frame" x="0.0" y="0.0" width="166" height="283"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="0.0"/> <size key="intercellSpacing" width="3" height="0.0"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns> <tableColumns>
<tableColumn width="164" minWidth="23" maxWidth="1000" id="ih9-mJ-EA7"> <tableColumn width="134" minWidth="23" maxWidth="1000" id="ih9-mJ-EA7">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
@ -340,7 +341,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews> <prototypeCellViews>
<tableCellView identifier="HeaderCell" id="qkt-WA-5tB"> <tableCellView identifier="HeaderCell" id="qkt-WA-5tB">
<rect key="frame" x="1" y="0.0" width="164" height="17"/> <rect key="frame" x="11" y="0.0" width="143" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fNJ-z1-0Up"> <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fNJ-z1-0Up">
@ -358,7 +359,7 @@
</connections> </connections>
</tableCellView> </tableCellView>
<tableCellView identifier="DataCell" id="HJn-Tm-YNO" customClass="SidebarCell" customModule="NetNewsWire" customModuleProvider="target"> <tableCellView identifier="DataCell" id="HJn-Tm-YNO" customClass="SidebarCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="1" y="17" width="164" height="17"/> <rect key="frame" x="11" y="17" width="143" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</tableCellView> </tableCellView>
</prototypeCellViews> </prototypeCellViews>
@ -616,9 +617,9 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="NSAddTemplate" width="11" height="11"/> <image name="NSAddTemplate" width="15" height="11"/>
<image name="NSRefreshTemplate" width="11" height="15"/> <image name="NSRefreshTemplate" width="14" height="14"/>
<image name="NSShareTemplate" width="11" height="16"/> <image name="NSShareTemplate" width="16" height="15"/>
<image name="cleanUp" width="149" height="113"/> <image name="cleanUp" width="149" height="113"/>
<image name="filterInactive" width="100" height="101"/> <image name="filterInactive" width="100" height="101"/>
<image name="markAllRead" width="22" height="19"/> <image name="markAllRead" width="22" height="19"/>

View File

@ -70,6 +70,10 @@ struct AppAssets {
return Image("ArticleExtractorOn") return Image("ArticleExtractorOn")
}() }()
static var checkmarkImage: Image = {
return Image(systemName: "checkmark")
}()
static var copyImage: Image = { static var copyImage: Image = {
return Image(systemName: "doc.on.doc") return Image(systemName: "doc.on.doc")
}() }()

View File

@ -170,7 +170,11 @@ final class AppDefaults: ObservableObject {
} }
// MARK: Timeline // MARK: Timeline
@AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool @AppStorage(wrappedValue: false, Key.timelineGroupByFeed, store: store) var timelineGroupByFeed: Bool {
didSet {
objectWillChange.send()
}
}
@AppStorage(wrappedValue: 2.0, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Double { @AppStorage(wrappedValue: 2.0, Key.timelineNumberOfLines, store: store) var timelineNumberOfLines: Double {
didSet { didSet {
@ -185,7 +189,11 @@ final class AppDefaults: ObservableObject {
} }
/// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`. /// Set to `true` to sort oldest to newest, `false` for newest to oldest. Default is `false`.
@AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool @AppStorage(wrappedValue: false, Key.timelineSortDirection, store: store) var timelineSortDirection: Bool {
didSet {
objectWillChange.send()
}
}
// MARK: Refresh // MARK: Refresh
@AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool @AppStorage(wrappedValue: false, Key.refreshClearsReadArticles, store: store) var refreshClearsReadArticles: Bool

View File

@ -55,25 +55,18 @@ class TimelineModel: ObservableObject, UndoableCommandRunner {
return _idToArticleDictionary return _idToArticleDictionary
} }
private var sortDirection = AppDefaults.shared.timelineSortDirection { private var sortDirection: Bool {
didSet { AppDefaults.shared.timelineSortDirection
if sortDirection != oldValue {
sortParametersDidChange()
}
}
} }
private var groupByFeed = AppDefaults.shared.timelineGroupByFeed { private var groupByFeed: Bool {
didSet { AppDefaults.shared.timelineGroupByFeed
if groupByFeed != oldValue {
sortParametersDidChange()
}
}
} }
init() { init() {
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
// TODO: This should be rewritten to use Combine correctly // TODO: This should be rewritten to use Combine correctly
selectedArticleIDsCancellable = $selectedArticleIDs.sink { [weak self] articleIDs in selectedArticleIDsCancellable = $selectedArticleIDs.sink { [weak self] articleIDs in
guard let self = self else { return } guard let self = self else { return }
@ -213,6 +206,13 @@ private extension TimelineModel {
} }
} }
@objc func userDefaultsDidChange(_ note: Notification) {
performBlockAndRestoreSelection {
articles = articles.sortedByDate(sortDirection ? .orderedDescending : .orderedAscending, groupByFeed: groupByFeed)
rebuildTimelineItems()
}
}
// MARK: Timeline Management // MARK: Timeline Management
func resetReadFilter() { func resetReadFilter() {
@ -233,13 +233,6 @@ private extension TimelineModel {
} }
} }
func sortParametersDidChange() {
performBlockAndRestoreSelection {
let unsortedArticles = Set(articles)
replaceArticles(with: unsortedArticles)
}
}
func performBlockAndRestoreSelection(_ block: (() -> Void)) { func performBlockAndRestoreSelection(_ block: (() -> Void)) {
// let savedSelection = selectedArticleIDs() // let savedSelection = selectedArticleIDs()
block() block()

View File

@ -0,0 +1,64 @@
//
// TimelineSortOrderView.swift
// Multiplatform macOS
//
// Created by Maurice Parker on 7/12/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import SwiftUI
struct TimelineSortOrderView: View {
@EnvironmentObject var settings: AppDefaults
@State var selection: Int = 1
var body: some View {
Menu {
Button {
settings.timelineSortDirection = true
} label: {
HStack {
Text("Newest to Oldest")
if settings.timelineSortDirection {
Spacer()
AppAssets.checkmarkImage
}
}
}
Button {
settings.timelineSortDirection = false
} label: {
HStack {
Text("Oldest to Newest")
if !settings.timelineSortDirection {
Spacer()
AppAssets.checkmarkImage
}
}
}
Divider()
Button {
settings.timelineGroupByFeed.toggle()
} label: {
HStack {
Text("Group by Feed")
if settings.timelineGroupByFeed {
Spacer()
AppAssets.checkmarkImage
}
}
}
} label : {
if settings.timelineSortDirection {
Text("Sort Newest to Oldest")
} else {
Text("Sort Oldest to Newest")
}
}
.font(.subheadline)
.frame(width: 150)
.padding(.top, 8).padding(.leading)
.menuStyle(BorderlessButtonMenuStyle())
}
}

View File

@ -17,6 +17,7 @@ struct TimelineView: View {
#if os(macOS) #if os(macOS)
VStack { VStack {
HStack { HStack {
TimelineSortOrderView()
Spacer() Spacer()
Button (action: { Button (action: {
withAnimation { withAnimation {

View File

@ -298,6 +298,7 @@
5193CD5A245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; 5193CD5A245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; }; 5194736E24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; };
5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; }; 5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; };
5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */; };
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; }; 519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; };
@ -1963,6 +1964,7 @@
51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = "<group>"; }; 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = "<group>"; };
5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RedditFeedProvider-Extensions.swift"; sourceTree = "<group>"; }; 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RedditFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
5194736D24BBB937001A2939 /* HiddenModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenModifier.swift; sourceTree = "<group>"; }; 5194736D24BBB937001A2939 /* HiddenModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenModifier.swift; sourceTree = "<group>"; };
5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSortOrderView.swift; sourceTree = "<group>"; };
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = "<group>"; }; 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionPointViewController.swift; sourceTree = "<group>"; };
@ -2833,13 +2835,14 @@
51919FCB24AB855000541E64 /* Timeline */ = { 51919FCB24AB855000541E64 /* Timeline */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5177470224B2657F00EB0F74 /* TimelineToolbarModifier.swift */,
51919FED24AB85E400541E64 /* TimelineContainerView.swift */, 51919FED24AB85E400541E64 /* TimelineContainerView.swift */,
51919FF324AB869C00541E64 /* TimelineItem.swift */, 51919FF324AB869C00541E64 /* TimelineItem.swift */,
514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */, 514E6C0124AD29A300AC6F6E /* TimelineItemStatusView.swift */,
514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */, 514E6BD924ACEA0400AC6F6E /* TimelineItemView.swift */,
51919FF024AB864A00541E64 /* TimelineModel.swift */, 51919FF024AB864A00541E64 /* TimelineModel.swift */,
5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */,
517B2EEA24B40E09001AC46C /* TimelineTitleModifier.swift */, 517B2EEA24B40E09001AC46C /* TimelineTitleModifier.swift */,
5177470224B2657F00EB0F74 /* TimelineToolbarModifier.swift */,
51919FF624AB8B7700541E64 /* TimelineView.swift */, 51919FF624AB8B7700541E64 /* TimelineView.swift */,
); );
path = Timeline; path = Timeline;
@ -5200,6 +5203,7 @@
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */, 514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */, 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */,
51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */, 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */,
5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */,
51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
51A5769724AE617200078888 /* ArticleContainerView.swift in Sources */, 51A5769724AE617200078888 /* ArticleContainerView.swift in Sources */,
51E4991224A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */, 51E4991224A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */,