Implement basic post store and list / editor UI components

This commit is contained in:
Angelo Stavrow 2020-07-25 07:02:11 -04:00
parent ddb94eb4d0
commit 896b5f73f7
No known key found for this signature in database
GPG Key ID: 1A49C7064E060EEE
12 changed files with 360 additions and 16 deletions

View File

@ -1,2 +1,4 @@
type_name: type_name:
allowed_symbols: ["_"] # Used in SwiftUI boilerplate naming allowed_symbols: ["_"] # Used in SwiftUI boilerplate naming
identifier_name:
excluded: ["id"] # Required for Identifiable conformance

View File

@ -1,13 +0,0 @@
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, WriteFreely!").padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

View File

@ -0,0 +1,9 @@
import SwiftUI
#if canImport(UIKit)
extension View {
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
#endif

View File

@ -0,0 +1,27 @@
import SwiftUI
struct ContentView: View {
@ObservedObject var postStore: PostStore
var body: some View {
NavigationView {
PostList(postStore: postStore)
.frame(maxHeight: .infinity)
.navigationTitle("Posts")
.toolbar {
NavigationLink(destination: PostEditor()) {
Image(systemName: "plus")
}
}
Text("Select a post, or create a new draft.")
.foregroundColor(.secondary)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(postStore: testPostStore)
}
}

53
Shared/Post/Post.swift Normal file
View File

@ -0,0 +1,53 @@
import Foundation
import WriteFreely
struct Post: Identifiable {
var id = UUID()
var title: String
var body: String
var createdDate: Date
var status: PostStatus = .draft
var editableText: String {
return """
# \(self.title)
\(self.body)
"""
}
}
let testPost = Post(
title: "Test Post Title",
body: """
Here's some cool sample body text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ultrices \
posuere dignissim. Vestibulum a libero tempor, lacinia nulla vitae, congue purus. Nunc ac nulla quam. Duis \
tincidunt eros augue, et volutpat tortor pulvinar ut. Nullam sit amet maximus urna. Phasellus non dignissim lacus.\
Nulla ac posuere ex. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec \
non molestie mauris. Suspendisse potenti. Vivamus at erat turpis.
Pellentesque porttitor gravida tincidunt. Sed vitae eros non metus aliquam hendrerit. Aliquam sed risus suscipit \
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)
let testPostData = [
Post(
title: "My First Post",
body: "Look at me, creating a first post! That's cool.",
createdDate: Date(timeIntervalSince1970: 1595429452),
status: .published
),
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
),
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)
)
]

View File

@ -0,0 +1,41 @@
import SwiftUI
struct PostCell: View {
var post: Post
var body: some View {
NavigationLink(
destination: PostEditor(
textString: post.editableText,
postStatus: post.status
)
) {
HStack {
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
.lineLimit(1)
Text(buildDateString(from: post.createdDate))
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}
Spacer()
PostStatusBadge(postStatus: post.status)
}
}
}
func buildDateString(from date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
return dateFormatter.string(from: date)
}
}
struct PostCell_Previews: PreviewProvider {
static var previews: some View {
PostCell(post: testPost)
}
}

View File

@ -0,0 +1,41 @@
//
// PostEditor.swift
// WriteFreely-MultiPlatform
//
// Created by Angelo Stavrow on 2020-07-24.
//
import SwiftUI
struct PostEditor: View {
@State var textString: String = ""
@State private var hasUnpublishedChanges: Bool = false
var postStatus: PostStatus = .draft
var body: some View {
TextEditor(text: $textString.animation())
.font(.body)
.padding()
.onChange(of: textString) { _ in
if postStatus == .published {
hasUnpublishedChanges = true
}
}
.toolbar {
if hasUnpublishedChanges {
PostStatusBadge(postStatus: .edited)
} else {
PostStatusBadge(postStatus: postStatus)
}
}
}
}
struct PostEditor_Previews: PreviewProvider {
static var previews: some View {
PostEditor(
textString: testPost.editableText,
postStatus: testPost.status
)
}
}

View File

@ -0,0 +1,21 @@
import SwiftUI
struct PostList: View {
var postStore: PostStore
var body: some View {
List {
Text("\(postStore.posts.count) Posts")
.foregroundColor(.secondary)
ForEach(postStore.posts) { post in
PostCell(post: post)
}
}
}
}
struct PostList_Previews: PreviewProvider {
static var previews: some View {
PostList(postStore: testPostStore)
}
}

View File

@ -0,0 +1,63 @@
import SwiftUI
enum PostStatus {
case draft
case edited
case published
}
struct PostStatusBadge: View {
@State var postStatus: PostStatus
var body: some View {
let (badgeLabel, badgeColor) = setupBadgeProperties(for: postStatus)
Text(badgeLabel)
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.white)
.textCase(.uppercase)
.lineLimit(1)
.padding(EdgeInsets(top: 2.5, leading: 7.5, bottom: 2.5, trailing: 7.5))
.background(badgeColor)
.clipShape(RoundedRectangle(cornerRadius: 5.0, style: .circular))
}
func setupBadgeProperties(for status: PostStatus) -> (String, Color) {
var badgeLabel: String
var badgeColor: Color
switch status {
case .draft:
badgeLabel = "draft"
badgeColor = Color(red: 0.75, green: 0.5, blue: 0.85, opacity: 1.0)
case .edited:
badgeLabel = "edited"
badgeColor = Color(red: 0.75, green: 0.7, blue: 0.1, opacity: 1.0)
case .published:
badgeLabel = "published"
badgeColor = .gray
}
return (badgeLabel, badgeColor)
}
}
struct PostStatusBadge_DraftPreviews: PreviewProvider {
static var previews: some View {
PostStatusBadge(postStatus: .draft)
}
}
struct PostStatusBadge_EditedPreviews: PreviewProvider {
static var previews: some View {
Group {
PostStatusBadge(postStatus: .edited)
}
}
}
struct PostStatusBadge_PublishedPreviews: PreviewProvider {
static var previews: some View {
PostStatusBadge(postStatus: .published)
}
}

View File

@ -0,0 +1,11 @@
import Foundation
class PostStore: ObservableObject {
@Published var posts: [Post]
init(posts: [Post] = []) {
self.posts = posts
}
}
let testPostStore = PostStore(posts: testPostData)

View File

@ -2,9 +2,10 @@ import SwiftUI
@main @main
struct WriteFreely_MultiPlatformApp: App { struct WriteFreely_MultiPlatformApp: App {
@StateObject private var store = PostStore()
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ContentView() ContentView(postStore: store)
} }
} }
} }

View File

@ -7,6 +7,17 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
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 */; };
1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostStore.swift */; };
1756AE7424CB26FA00FD7257 /* PostCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCell.swift */; };
1756AE7524CB26FA00FD7257 /* PostCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCell.swift */; };
1756AE7724CB2EDD00FD7257 /* PostEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7624CB2EDD00FD7257 /* PostEditor.swift */; };
1756AE7824CB2EDD00FD7257 /* PostEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7624CB2EDD00FD7257 /* PostEditor.swift */; };
1756AE7A24CB65DF00FD7257 /* PostList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7924CB65DF00FD7257 /* PostList.swift */; };
1756AE7B24CB65DF00FD7257 /* PostList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7924CB65DF00FD7257 /* PostList.swift */; };
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE8024CB844500FD7257 /* View+Keyboard.swift */; };
17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */; }; 17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */; };
17DF32A824C87D3500BCE2E3 /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */; }; 17DF32A824C87D3500BCE2E3 /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */; };
17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */; }; 17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */; };
@ -17,6 +28,8 @@
17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; }; 17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; };
17DF32C024C87D7B00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32BF24C87D7B00BCE2E3 /* WriteFreely */; }; 17DF32C024C87D7B00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32BF24C87D7B00BCE2E3 /* WriteFreely */; };
17DF32C324C87D8D00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32C224C87D8D00BCE2E3 /* WriteFreely */; }; 17DF32C324C87D8D00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32C224C87D8D00BCE2E3 /* WriteFreely */; };
17DF32D524C8CA3400BCE2E3 /* PostStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadge.swift */; };
17DF32D624C8CA3400BCE2E3 /* PostStatusBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadge.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -37,6 +50,12 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
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>"; };
1756AE7624CB2EDD00FD7257 /* PostEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditor.swift; sourceTree = "<group>"; };
1756AE7924CB65DF00FD7257 /* PostList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostList.swift; sourceTree = "<group>"; };
1756AE8024CB844500FD7257 /* View+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Keyboard.swift"; sourceTree = "<group>"; };
17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreely_MultiPlatformApp.swift; sourceTree = "<group>"; }; 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreely_MultiPlatformApp.swift; sourceTree = "<group>"; };
17DF328224C87D3300BCE2E3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; 17DF328224C87D3300BCE2E3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
17DF328324C87D3500BCE2E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 17DF328324C87D3500BCE2E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -56,6 +75,7 @@
17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; }; 17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
17DF32C924C8855E00BCE2E3 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; }; 17DF32C924C8855E00BCE2E3 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; }; 17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
17DF32D424C8CA3400BCE2E3 /* PostStatusBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatusBadge.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -92,6 +112,14 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
1756AE7F24CB841200FD7257 /* Extensions */ = {
isa = PBXGroup;
children = (
1756AE8024CB844500FD7257 /* View+Keyboard.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
17DF327B24C87D3300BCE2E3 = { 17DF327B24C87D3300BCE2E3 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -114,8 +142,13 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */, 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */,
17DF328224C87D3300BCE2E3 /* ContentView.swift */,
17DF328324C87D3500BCE2E3 /* Assets.xcassets */, 17DF328324C87D3500BCE2E3 /* Assets.xcassets */,
17DF32D024C8B75C00BCE2E3 /* Model */,
1756AE7F24CB841200FD7257 /* Extensions */,
17DF32CC24C8B72300BCE2E3 /* Navigation */,
17DF32D224C8B78D00BCE2E3 /* Collection */,
17DF32D124C8B78500BCE2E3 /* Post */,
17DF32D324C8C9F600BCE2E3 /* Components */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -173,6 +206,48 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
17DF32CC24C8B72300BCE2E3 /* Navigation */ = {
isa = PBXGroup;
children = (
17DF328224C87D3300BCE2E3 /* ContentView.swift */,
);
path = Navigation;
sourceTree = "<group>";
};
17DF32D024C8B75C00BCE2E3 /* Model */ = {
isa = PBXGroup;
children = (
);
path = Model;
sourceTree = "<group>";
};
17DF32D124C8B78500BCE2E3 /* Post */ = {
isa = PBXGroup;
children = (
1756AE6A24CB1E4B00FD7257 /* Post.swift */,
1756AE7324CB26FA00FD7257 /* PostCell.swift */,
1756AE7624CB2EDD00FD7257 /* PostEditor.swift */,
1756AE7924CB65DF00FD7257 /* PostList.swift */,
1756AE6D24CB255B00FD7257 /* PostStore.swift */,
17DF32D424C8CA3400BCE2E3 /* PostStatusBadge.swift */,
);
path = Post;
sourceTree = "<group>";
};
17DF32D224C8B78D00BCE2E3 /* Collection */ = {
isa = PBXGroup;
children = (
);
path = Collection;
sourceTree = "<group>";
};
17DF32D324C8C9F600BCE2E3 /* Components */ = {
isa = PBXGroup;
children = (
);
path = Components;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -379,7 +454,14 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */, 17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */,
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */,
1756AE7724CB2EDD00FD7257 /* PostEditor.swift in Sources */,
17DF32D524C8CA3400BCE2E3 /* PostStatusBadge.swift in Sources */,
1756AE7A24CB65DF00FD7257 /* PostList.swift in Sources */,
17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */,
1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */,
1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */,
1756AE7424CB26FA00FD7257 /* PostCell.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -388,7 +470,13 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */, 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */,
1756AE7824CB2EDD00FD7257 /* PostEditor.swift in Sources */,
17DF32D624C8CA3400BCE2E3 /* PostStatusBadge.swift in Sources */,
1756AE7B24CB65DF00FD7257 /* PostList.swift in Sources */,
17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */,
1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */,
1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */,
1756AE7524CB26FA00FD7257 /* PostCell.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };