Browse Source

Implement basic post store and list / editor UI components

tags/v0.0.2
Angelo Stavrow 3 years ago
parent
commit
896b5f73f7
No known key found for this signature in database GPG Key ID: 1A49C7064E060EEE
12 changed files with 360 additions and 16 deletions
  1. +3
    -1
      .swiftlint.yml
  2. +0
    -13
      Shared/ContentView.swift
  3. +9
    -0
      Shared/Extensions/View+Keyboard.swift
  4. +27
    -0
      Shared/Navigation/ContentView.swift
  5. +53
    -0
      Shared/Post/Post.swift
  6. +41
    -0
      Shared/Post/PostCell.swift
  7. +41
    -0
      Shared/Post/PostEditor.swift
  8. +21
    -0
      Shared/Post/PostList.swift
  9. +63
    -0
      Shared/Post/PostStatusBadge.swift
  10. +11
    -0
      Shared/Post/PostStore.swift
  11. +2
    -1
      Shared/WriteFreely_MultiPlatformApp.swift
  12. +89
    -1
      WriteFreely-MultiPlatform.xcodeproj/project.pbxproj

+ 3
- 1
.swiftlint.yml View File

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

+ 0
- 13
Shared/ContentView.swift 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()
}
}

+ 9
- 0
Shared/Extensions/View+Keyboard.swift 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

+ 27
- 0
Shared/Navigation/ContentView.swift 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
- 0
Shared/Post/Post.swift 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)
)
]

+ 41
- 0
Shared/Post/PostCell.swift 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)
}
}

+ 41
- 0
Shared/Post/PostEditor.swift 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
)
}
}

+ 21
- 0
Shared/Post/PostList.swift 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)
}
}

+ 63
- 0
Shared/Post/PostStatusBadge.swift 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)
}
}

+ 11
- 0
Shared/Post/PostStore.swift 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)

+ 2
- 1
Shared/WriteFreely_MultiPlatformApp.swift View File

@@ -2,9 +2,10 @@ import SwiftUI

@main
struct WriteFreely_MultiPlatformApp: App {
@StateObject private var store = PostStore()
var body: some Scene {
WindowGroup {
ContentView()
ContentView(postStore: store)
}
}
}

+ 89
- 1
WriteFreely-MultiPlatform.xcodeproj/project.pbxproj View File

@@ -7,6 +7,17 @@
objects = {

/* 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 */; };
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 */; };
@@ -17,6 +28,8 @@
17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; };
17DF32C024C87D7B00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32BF24C87D7B00BCE2E3 /* 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 */

/* Begin PBXContainerItemProxy section */
@@ -37,6 +50,12 @@
/* End PBXContainerItemProxy 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>"; };
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>"; };
@@ -56,6 +75,7 @@
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>"; };
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 */

/* Begin PBXFrameworksBuildPhase section */
@@ -92,6 +112,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
1756AE7F24CB841200FD7257 /* Extensions */ = {
isa = PBXGroup;
children = (
1756AE8024CB844500FD7257 /* View+Keyboard.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
17DF327B24C87D3300BCE2E3 = {
isa = PBXGroup;
children = (
@@ -114,8 +142,13 @@
isa = PBXGroup;
children = (
17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */,
17DF328224C87D3300BCE2E3 /* ContentView.swift */,
17DF328324C87D3500BCE2E3 /* Assets.xcassets */,
17DF32D024C8B75C00BCE2E3 /* Model */,
1756AE7F24CB841200FD7257 /* Extensions */,
17DF32CC24C8B72300BCE2E3 /* Navigation */,
17DF32D224C8B78D00BCE2E3 /* Collection */,
17DF32D124C8B78500BCE2E3 /* Post */,
17DF32D324C8C9F600BCE2E3 /* Components */,
);
path = Shared;
sourceTree = "<group>";
@@ -173,6 +206,48 @@
name = Frameworks;
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 */

/* Begin PBXNativeTarget section */
@@ -379,7 +454,14 @@
buildActionMask = 2147483647;
files = (
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 */,
1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */,
1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */,
1756AE7424CB26FA00FD7257 /* PostCell.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -388,7 +470,13 @@
buildActionMask = 2147483647;
files = (
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 */,
1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */,
1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */,
1756AE7524CB26FA00FD7257 /* PostCell.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};


Loading…
Cancel
Save