mirror of
https://github.com/writeas/writefreely-swiftui-multiplatform.git
synced 2024-11-15 01:11:02 +00:00
Merge pull request #3 from writeas/add-collections-sidebar
Add collections sidebar
This commit is contained in:
commit
ea1243b8ab
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Post editor now has a Publish button to change post status
|
||||
- Collections sidebar to choose a specific collection (i.e., blog)
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -5,14 +5,9 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
PostList()
|
||||
.frame(maxHeight: .infinity)
|
||||
.navigationTitle("Posts")
|
||||
.toolbar {
|
||||
NavigationLink(destination: PostEditor(post: Post())) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
CollectionSidebar()
|
||||
|
||||
PostList(selectedCollection: allPostsCollection)
|
||||
|
||||
Text("Select a post, or create a new draft.")
|
||||
.foregroundColor(.secondary)
|
||||
|
@ -1,5 +1,4 @@
|
||||
import Foundation
|
||||
import WriteFreely
|
||||
|
||||
enum PostStatus {
|
||||
case draft
|
||||
@ -12,6 +11,7 @@ class Post: Identifiable, ObservableObject {
|
||||
@Published var body: String
|
||||
@Published var createdDate: Date
|
||||
@Published var status: PostStatus
|
||||
@Published var collection: PostCollection
|
||||
|
||||
let id = UUID()
|
||||
|
||||
@ -19,12 +19,14 @@ class Post: Identifiable, ObservableObject {
|
||||
title: String = "Title",
|
||||
body: String = "Write your post here...",
|
||||
createdDate: Date = Date(),
|
||||
status: PostStatus = .draft
|
||||
status: PostStatus = .draft,
|
||||
collection: PostCollection = defaultDraftCollection
|
||||
) {
|
||||
self.title = title
|
||||
self.body = body
|
||||
self.createdDate = createdDate
|
||||
self.status = status
|
||||
self.collection = collection
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,25 +43,45 @@ let testPost = Post(
|
||||
turpis dictum dictum. Duis lacus lectus, dictum vel felis in, rhoncus fringilla felis. Nunc id dolor nisl. Aliquam \
|
||||
euismod purus elit. Nullam egestas neque leo, sed aliquet ligula ultrices nec.
|
||||
""",
|
||||
createdDate: Date(),
|
||||
status: .published)
|
||||
createdDate: Date()
|
||||
)
|
||||
|
||||
let testPostData = [
|
||||
Post(
|
||||
title: "My First Post",
|
||||
body: "Look at me, creating a first post! That's cool.",
|
||||
createdDate: Date(timeIntervalSince1970: 1595429452),
|
||||
status: .published
|
||||
status: .published,
|
||||
collection: userCollections[0]
|
||||
),
|
||||
Post(
|
||||
title: "Post 2: The Quickening",
|
||||
body: "See, here's the rule about Highlander jokes: _there can be only one_.",
|
||||
createdDate: Date(timeIntervalSince1970: 1595514125),
|
||||
status: .edited
|
||||
status: .edited,
|
||||
collection: userCollections[0]
|
||||
),
|
||||
Post(
|
||||
title: "The Post Revolutions",
|
||||
body: "I can never keep the Matrix movie order straight. Why not just call them part 2 and part 3?",
|
||||
createdDate: Date(timeIntervalSince1970: 1595600006)
|
||||
),
|
||||
Post(
|
||||
title: "Episode IV: A New Post",
|
||||
body: "How many movies does this person watch? How many movie-title jokes will they make?",
|
||||
createdDate: Date(timeIntervalSince1970: 1596219877),
|
||||
status: .published,
|
||||
collection: userCollections[1]
|
||||
),
|
||||
Post(
|
||||
title: "Fast (Post) Five",
|
||||
body: "Look, it was either a Fast and the Furious reference, or a Resident Evil reference."
|
||||
),
|
||||
Post(
|
||||
title: "Post: The Final Chapter",
|
||||
body: "And there you have it, a Resident Evil movie reference.",
|
||||
createdDate: Date(timeIntervalSince1970: 1596043684),
|
||||
status: .edited,
|
||||
collection: userCollections[2]
|
||||
)
|
||||
]
|
||||
|
@ -5,23 +5,19 @@ struct PostCell: View {
|
||||
@ObservedObject var post: Post
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(
|
||||
destination: PostEditor(post: post)
|
||||
) {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(post.title)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
Text(buildDateString(from: post.createdDate))
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer()
|
||||
PostStatusBadge(post: post)
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(post.title)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
Text(buildDateString(from: post.createdDate))
|
||||
.font(.caption)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer()
|
||||
PostStatusBadge(post: post)
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
|
||||
func buildDateString(from date: Date) -> String {
|
||||
|
@ -2,13 +2,80 @@ import SwiftUI
|
||||
|
||||
struct PostList: View {
|
||||
@EnvironmentObject var postStore: PostStore
|
||||
@State var selectedCollection: PostCollection
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
List {
|
||||
Text("\(postStore.posts.count) Posts")
|
||||
.foregroundColor(.secondary)
|
||||
ForEach(postStore.posts) { post in
|
||||
PostCell(post: post)
|
||||
ForEach(showPosts(for: selectedCollection)) { post in
|
||||
NavigationLink(
|
||||
destination: PostEditor(post: post)
|
||||
) {
|
||||
PostCell(
|
||||
post: post
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(selectedCollection.title)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button(action: {
|
||||
let post = Post()
|
||||
postStore.add(post)
|
||||
}, label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
})
|
||||
}
|
||||
ToolbarItem(placement: .bottomBar) {
|
||||
Spacer()
|
||||
}
|
||||
ToolbarItem(placement: .bottomBar) {
|
||||
Text(pluralizedPostCount(for: showPosts(for: selectedCollection)))
|
||||
}
|
||||
ToolbarItem(placement: .bottomBar) {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
#else //if os(macOS)
|
||||
List {
|
||||
ForEach(showPosts(for: selectedCollection)) { post in
|
||||
NavigationLink(
|
||||
destination: PostEditor(post: post)
|
||||
) {
|
||||
PostCell(
|
||||
post: post
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(selectedCollection.title)
|
||||
.navigationSubtitle(pluralizedPostCount(for: showPosts(for: selectedCollection)))
|
||||
.toolbar {
|
||||
Button(action: {
|
||||
let post = Post()
|
||||
postStore.add(post)
|
||||
}, label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
})
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func pluralizedPostCount(for posts: [Post]) -> String {
|
||||
if posts.count == 1 {
|
||||
return "1 post"
|
||||
} else {
|
||||
return "\(posts.count) posts"
|
||||
}
|
||||
}
|
||||
|
||||
private func showPosts(for collection: PostCollection) -> [Post] {
|
||||
if collection == allPostsCollection {
|
||||
return postStore.posts
|
||||
} else {
|
||||
return postStore.posts.filter {
|
||||
$0.collection.title == collection.title
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +83,9 @@ struct PostList: View {
|
||||
|
||||
struct PostList_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PostList()
|
||||
.environmentObject(testPostStore)
|
||||
Group {
|
||||
PostList(selectedCollection: allPostsCollection)
|
||||
.environmentObject(testPostStore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ struct PostStatusBadge: View {
|
||||
let (badgeLabel, badgeColor) = setupBadgeProperties(for: post.status)
|
||||
Text(badgeLabel)
|
||||
.font(.caption)
|
||||
.fontWeight(.bold)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.white)
|
||||
.textCase(.uppercase)
|
||||
.lineLimit(1)
|
||||
|
36
Shared/PostCollection/CollectionPicker.swift
Normal file
36
Shared/PostCollection/CollectionPicker.swift
Normal file
@ -0,0 +1,36 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CollectionSidebar: View {
|
||||
@EnvironmentObject var postStore: PostStore
|
||||
@Binding var selectedCollection: PostCollection?
|
||||
|
||||
private let collections = [
|
||||
allPostsCollection,
|
||||
defaultDraftCollection,
|
||||
testPostCollection1,
|
||||
testPostCollection2,
|
||||
testPostCollection3
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(collections) { collection in
|
||||
NavigationLink(
|
||||
destination: PostList(title: collection.title, posts: showPosts(for: collection)).tag(collection)) {
|
||||
Text(collection.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(SidebarListStyle())
|
||||
}
|
||||
|
||||
func showPosts(for collection: PostCollection) -> [Post] {
|
||||
if collection == allPostsCollection {
|
||||
return postStore.posts
|
||||
} else {
|
||||
return postStore.posts.filter {
|
||||
$0.collection.title == collection.title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
Shared/PostCollection/CollectionSidebar.swift
Normal file
25
Shared/PostCollection/CollectionSidebar.swift
Normal file
@ -0,0 +1,25 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CollectionSidebar: View {
|
||||
private let collections = postCollections
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(collections) { collection in
|
||||
NavigationLink(
|
||||
destination: PostList(selectedCollection: collection)
|
||||
) {
|
||||
Text(collection.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Collections")
|
||||
.listStyle(SidebarListStyle())
|
||||
}
|
||||
}
|
||||
|
||||
struct CollectionSidebar_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CollectionSidebar()
|
||||
}
|
||||
}
|
22
Shared/PostCollection/PostCollection.swift
Normal file
22
Shared/PostCollection/PostCollection.swift
Normal file
@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
struct PostCollection: Identifiable, Hashable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
}
|
||||
|
||||
let allPostsCollection = PostCollection(title: "All Posts")
|
||||
let defaultDraftCollection = PostCollection(title: "Drafts")
|
||||
let userCollections = [
|
||||
PostCollection(title: "Collection 1"),
|
||||
PostCollection(title: "Collection 2"),
|
||||
PostCollection(title: "Collection 3")
|
||||
]
|
||||
|
||||
let postCollections = [
|
||||
allPostsCollection,
|
||||
defaultDraftCollection,
|
||||
userCollections[0],
|
||||
userCollections[1],
|
||||
userCollections[2]
|
||||
]
|
@ -2,7 +2,11 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct WriteFreely_MultiPlatformApp: App {
|
||||
#if DEBUG
|
||||
@StateObject private var store = testPostStore
|
||||
#else
|
||||
@StateObject private var store = PostStore()
|
||||
#endif
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
|
@ -7,6 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
171BFDF724D49FD400888236 /* PostCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF624D49FD400888236 /* PostCollection.swift */; };
|
||||
171BFDF824D49FD400888236 /* PostCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF624D49FD400888236 /* PostCollection.swift */; };
|
||||
171BFDFA24D4AF8300888236 /* CollectionSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionSidebar.swift */; };
|
||||
171BFDFB24D4AF8300888236 /* CollectionSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionSidebar.swift */; };
|
||||
1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6A24CB1E4B00FD7257 /* Post.swift */; };
|
||||
1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6A24CB1E4B00FD7257 /* Post.swift */; };
|
||||
1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostStore.swift */; };
|
||||
@ -50,6 +54,8 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
171BFDF624D49FD400888236 /* PostCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCollection.swift; sourceTree = "<group>"; };
|
||||
171BFDF924D4AF8300888236 /* CollectionSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionSidebar.swift; sourceTree = "<group>"; };
|
||||
1756AE6A24CB1E4B00FD7257 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
||||
1756AE6D24CB255B00FD7257 /* PostStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStore.swift; sourceTree = "<group>"; };
|
||||
1756AE7324CB26FA00FD7257 /* PostCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCell.swift; sourceTree = "<group>"; };
|
||||
@ -237,6 +243,8 @@
|
||||
17DF32D224C8B78D00BCE2E3 /* PostCollection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
171BFDF624D49FD400888236 /* PostCollection.swift */,
|
||||
171BFDF924D4AF8300888236 /* CollectionSidebar.swift */,
|
||||
);
|
||||
path = PostCollection;
|
||||
sourceTree = "<group>";
|
||||
@ -455,9 +463,11 @@
|
||||
files = (
|
||||
17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */,
|
||||
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */,
|
||||
171BFDFA24D4AF8300888236 /* CollectionSidebar.swift in Sources */,
|
||||
1756AE7724CB2EDD00FD7257 /* PostEditor.swift in Sources */,
|
||||
17DF32D524C8CA3400BCE2E3 /* PostStatusBadge.swift in Sources */,
|
||||
1756AE7A24CB65DF00FD7257 /* PostList.swift in Sources */,
|
||||
171BFDF724D49FD400888236 /* PostCollection.swift in Sources */,
|
||||
17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */,
|
||||
1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */,
|
||||
1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */,
|
||||
@ -469,10 +479,12 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
171BFDF824D49FD400888236 /* PostCollection.swift in Sources */,
|
||||
17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */,
|
||||
1756AE7824CB2EDD00FD7257 /* PostEditor.swift in Sources */,
|
||||
17DF32D624C8CA3400BCE2E3 /* PostStatusBadge.swift in Sources */,
|
||||
1756AE7B24CB65DF00FD7257 /* PostList.swift in Sources */,
|
||||
171BFDFB24D4AF8300888236 /* CollectionSidebar.swift in Sources */,
|
||||
17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */,
|
||||
1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */,
|
||||
1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */,
|
||||
|
@ -7,12 +7,12 @@
|
||||
<key>WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
Loading…
Reference in New Issue
Block a user