implement Task planner App

Pub Date: 2023-07-30

Drag fons into Bundle Resources ![image-20230730195636561](../../img_list/image20230730195636561.png) Add font key to Info.plist ![image-20230730201103891](../../img_list/image20230730201103891.png) ```xml ATSApplicationFontsPath UIAppFonts ubuntu.bold.ttf ubuntu.medium.ttf ubuntu.light.ttf ubuntu.regular.ttf ``` ## Model/Ubuntu.swift ```swift import SwiftUI /// MARK :custom font extension enum Ubuntu{ case light case bold case medium case regular var weight:Font.Weight{ switch self{ case .light: return .light case .bold: return .bold case .medium: return .medium case .regular: return .regular } } } extension View{ func Ubuntu(_ size:CGFloat,_ weight:Ubuntu)->some View{ self.font(.custom("ubuntu", size: size)) .fontWeight(weight.weight) } } ``` ## Model/Task.swift ```swift import SwiftUI //MARK:Task model struct Task:Identifiable{ var id:UUID = UUID() var dateadded:Date var taskName:String var taskDescription:String var taskCategory:Category } enum Category:String, CaseIterable{ case bug="bug" case general="general" case idea="idea" case challenge="challenge" case coding="coding" case modifiers="modifiers"

var color:Color{ switch self{ case .bug: return Color.green case .general: return Color.gray case .idea: return Color.pink case .challenge: return Color.purple case .coding: return Color.brown case .modifiers: return Color.cyan } } } var sampleTasks:[Task] = [ .init(dateadded: Date(timeIntervalSince1970:1672948935), taskName: “Learn Swift”, taskDescription: “”, taskCategory: .coding), .init(dateadded: Date(timeIntervalSince1970:1672946935), taskName: “Learn Swift”, taskDescription: “”, taskCategory: .coding), .init(dateadded: Date(timeIntervalSince1970:1672946935), taskName: “Learn Swift”, taskDescription: “”, taskCategory: .coding), .init(dateadded: Date(timeIntervalSince1970:1672936935), taskName: “Learn Swift”, taskDescription: “”, taskCategory: .coding), .init(dateadded: Date(timeIntervalSince1970:1672946935), taskName: “Learn Swift”, taskDescription: “”, taskCategory: .coding) ]

## View/Home.swift
```swift
import SwiftUI
struct Home: View {
/// View propertyes
@State private var currentDay:Date = .init()
@State private var tasks:[Task] = sampleTasks
@State private var addNewTask:Bool = false
var body: some View {
ScrollView(.vertical,showsIndicators: false){
TimelineView().padding(15)
}.safeAreaInset(edge: .top,spacing: 0){
HeaderView()
}.fullScreenCover(isPresented: $addNewTask){
AddNewView{task in
//simply add to tasks
tasks.append(task)
}
}
}
/// - Time Line View
@ViewBuilder
func TimelineView()->some View{
VStack{
let hovers = Calendar.current.hovers
ForEach(hovers,id:\.self){hover in
TimelineViewRow(hover)
}
}

}
@ViewBuilder
func TimelineViewRow(_ date:Date)->some View{
HStack(alignment: .top){
Text(date.toString("h a")).Ubuntu(14, .regular).frame(width: 45,alignment: .leading)

///Filtring taks
let calendar = Calendar.current
let filteredTasks =  tasks.filter{
if let hour = calendar.dateComponents([.hour], from:date ).hour,
let taskHour = calendar.dateComponents([.hour], from: $0.dateadded).hour,
hour==taskHour && calendar.isDate($0.dateadded, inSameDayAs: currentDay){
return true
}
return false
}
if filteredTasks.isEmpty{
Rectangle().stroke(.gray.opacity(0.5),style: StrokeStyle(lineWidth: 0.5,lineCap: .butt,lineJoin: .bevel,dash: [5],dashPhase: 5))
.frame(height: 0.5)
.offset(y:10)
}else{
VStack(spacing: 10){
ForEach(filteredTasks){task in
TaskRow(task)

}
}
}

}.hAlign(.leading)
.padding(.vertical,15)
}
@ViewBuilder
func TaskRow(_ task:Task)->some View{
VStack(alignment: .leading,spacing: 8){
Text(task.taskName.uppercased()).Ubuntu(16, .regular).foregroundColor(task.taskCategory.color)
if task.taskDescription != ""{
Text(task.taskDescription).Ubuntu(14, .light)
.foregroundColor(task.taskCategory.color.opacity(0.8))
}
}.hAlign(.leading)
.padding(12)
.background{
ZStack(alignment: .leading){
Rectangle().fill(task.taskCategory.color).frame(width: 4)
Rectangle().fill(task.taskCategory.color.opacity(0.25 ))
}
}

}
/// - Header View
@ViewBuilder
func HeaderView()->some View{
VStack{
HStack{
VStack(alignment: .leading,spacing: 6){
Text("今天").Ubuntu(30, .regular)

Text("欢迎使用日历计划App").Ubuntu(14, .light)
}.hAlign(.leading)
Button{
addNewTask.toggle()
}label: {
HStack(spacing: 10){
Image(systemName: "plus")
Text("添加任务").Ubuntu(15, .regular)
}
}.padding(.vertical,10).padding(.horizontal,15).background{
Capsule().fill(Color.blue.gradient)
}.foregroundColor(.white)
}

/// Today date in string
Text(Date().toString("MMM YYYY")).Ubuntu(16, .medium).hAlign(.leading)
.padding(.top,15)


//Week row
WeekRowView()

}.padding(15)
.background{
VStack(spacing: 0){
Color.white
Rectangle().fill(.linearGradient(colors: [.white,.clear], startPoint: .top, endPoint: .bottom))
.frame(height: 20)
}.ignoresSafeArea()
}
}
/// - Week Row View
@ViewBuilder
func WeekRowView()->some View{
HStack(spacing: 0){
ForEach(Calendar.current.currentWeek){ weekDay in
let staus = Calendar.current.isDate(weekDay.date, inSameDayAs: currentDay)
VStack{
Text(weekDay.string.prefix(3))
.Ubuntu(13, .medium)
Text(weekDay.date.toString("dd"))
.Ubuntu(16, .regular)
}
.foregroundColor(staus ? Color.blue:Color.gray)
.hAlign(.center)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.easeInOut(duration: 0.3)){
currentDay = weekDay.date
}
}
}
}.padding(.horizontal,-15)
}


}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
///MARK:View extensions
extension View{
func hAlign(_ alignment:Alignment)->some View{
self.frame(maxWidth: .infinity,alignment: alignment)
}
func vAlign(_ alignment:Alignment)->some View{
self.frame(maxHeight: .infinity,alignment: alignment)
}
}
///MARK:Date extension
extension Date{
func toString(_ format:String)->String{
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(from: self)
}

}
///MARK:Calendar extension
extension Calendar{
///return 24 hover in a day
var hovers:[Date]{
let startOfDay = self.startOfDay(for: Date())
var hovers:[Date] = []
for index in 0..<24{
if let date = self.date(byAdding: .hour, value: index, to: startOfDay){
hovers.append(date)
}
}
return hovers
}

/// return current week in Array format
var currentWeek:[WeekDay]{
guard let firstWeekDay = self.dateInterval(of: .weekOfMonth, for: Date())?.start else{
return []
}
var week:[WeekDay] = []
for index in 0..<7{
if let day = self.date(byAdding: .day, value: index, to: firstWeekDay){
let weekDaySymbol:String = day.toString("EEEE")
let isToday = self.isDateInToday(day)
week.append(.init(string: weekDaySymbol, date: day,isToday: isToday))
}
}
return week
}
struct WeekDay:Identifiable{
var id:UUID = .init()
var string:String
var date:Date
var isToday:Bool = false
}
}

View/AddNewTaskView.swift

import SwiftUI
struct AddNewView: View {
/// - Calback
var onAdd: ((Task) -> ())
/// -View properies
@Environment(\.dismiss) private var dismiss
@State private var taskName:String = ""
@State private var taskDescription:String = ""
@State private var taskDate:Date = .init()
@State private var taskCategory:Category = .general
//Category animation properties
@State private var animatedColor:Color = Category.general.color
@State private var animate:Bool = false
var body: some View {
VStack(alignment: .leading){
VStack(alignment: .leading,spacing: 10){
//back button
Button(action: {
dismiss()
}, label: {
Image(systemName: "chevron.left").foregroundColor(.white).contentShape(Rectangle())
})
//create new task
Text("添加任务").Ubuntu(28, .light).foregroundColor(.white).padding(.vertical,15)
TitleView("名字")

TextField("备考雅思",text:$taskName).Ubuntu(16, .regular).tint(.white).padding(.top,2)

Rectangle().frame(height: 1).foregroundColor(.white.opacity(0.7))

TitleView("日期")

HStack(alignment: .bottom,spacing: 12 ){
HStack(spacing: 12){
Text(taskDate.toString("EEEE dd,MMMM"))

//Date picker
Image(systemName: "calendar").font(.title).foregroundColor(.white).overlay{
DatePicker("",selection: $taskDate,displayedComponents: [.date]).blendMode(.destinationOver)
}
}
.offset(y:-5)
.overlay(alignment: .bottom){
Rectangle().fill(.white.opacity(0.7)).frame(height: 1).offset(y:5)
}

HStack(spacing: 12){
Text(taskDate.toString("hh:MM a"))

//Date picker
Image(systemName: "clock").font(.title).foregroundColor(.white).overlay{
DatePicker("",selection: $taskDate,displayedComponents: [.hourAndMinute]).blendMode(.destinationOver)
}
}
.offset(y:-5)
.overlay(alignment: .bottom){
Rectangle().fill(.white.opacity(0.7)).frame(height: 1).offset(y:5)
}
}.padding(.bottom,15)

}
.environment(\.colorScheme, .dark)
.hAlign(.leading).padding(15).background{
ZStack{
taskCategory.color.ignoresSafeArea()

GeometryReader{
let size = $0.size
Rectangle().fill(animatedColor).mask{
Circle()
}.frame(width: animate ? size.width*2:0,height: animate ? size.height*2:0).offset(animate ? CGSize(width: -size.width/2, height: -size.height/2):size)
}.clipped()
}


}

//discription
VStack(alignment: .leading,spacing: 10){
TitleView("描述",.gray)

TextField("关于任务描述",text:$taskDescription).Ubuntu(16, .regular).tint(.white).padding(.top,2)

Rectangle().fill(.black.opacity(0.2)).frame(height: 1)

TitleView("分类",.gray).padding(.top,15)

LazyVGrid(columns: Array(repeating: .init(.flexible(),spacing: 20),count: 3),spacing: 15){
ForEach(Category.allCases,id:\.rawValue){category in
Text(category.rawValue.uppercased()).Ubuntu(12, .regular).padding(.vertical,5).background{
RoundedRectangle(cornerRadius: 5,style:.continuous).fill(category.color.opacity(0.3)).frame(width: 85)
}.foregroundColor(category.color).contentShape(Rectangle()).onTapGesture {
guard !animate else{
return
}
animatedColor = category.color
withAnimation(.interactiveSpring(response: 0.7,dampingFraction: 1,blendDuration: 1)){
animate = true
}
DispatchQueue.main.asyncAfter(deadline: .now()+0.7){
animate = false
taskCategory = category
}
}
}
}.padding(.top,5)

Button{
//get all the Task date and pass it to callback
let task  = Task(dateadded: taskDate, taskName: taskName, taskDescription: taskDescription, taskCategory: taskCategory)
(onAdd(task))
dismiss()
} label: {
Text("添加任务").Ubuntu(16, .regular).foregroundColor(.white).padding(.vertical,15).hAlign(.center).background{
Capsule().fill(animatedColor.gradient)
}
}.vAlign(.bottom).disabled(taskName == ""||animate).opacity(taskName == "" ? 0.6:1)
}.padding(15)
}.vAlign(.top)
}

@ViewBuilder
func TitleView(_ value:String,_ color:Color = .white.opacity(0.7))->some View{
Text(value).Ubuntu(16, .regular)
.foregroundColor(color)
}
}
struct AddNewView_Previews: PreviewProvider {
static var previews: some View {
AddNewView{task in

}
}
}

Simulator Screenshot - iPhone 14 Pro - 2023-07-30 at 21.34.44 Simulator Screenshot - iPhone 14 Pro - 2023-07-30 at 21.35.49