mirror of
https://github.com/pboulch/luto.git
synced 2025-12-19 04:43:08 +00:00
🎉 first commit
This commit is contained in:
@ -1,6 +1,15 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0",
|
||||
"green" : "205",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Luto
|
||||
//
|
||||
// Created by Pierre Boulc'h on 04/07/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Hello, world!")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
@ -9,9 +9,51 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct LutoApp: App {
|
||||
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
WindowGroup(id: "MainWindow") {
|
||||
MainView(taskTitle: "", taskDescription: "", listTask: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
|
||||
private var statusItem: NSStatusItem!
|
||||
private var popover: NSPopover!
|
||||
|
||||
@MainActor func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
|
||||
if let window = NSApplication.shared.windows.first {
|
||||
window.close()
|
||||
}
|
||||
|
||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
|
||||
if let statusButton = statusItem.button {
|
||||
statusButton.image = NSImage(systemSymbolName: "brain", accessibilityDescription: "Chart Line")
|
||||
statusButton.action = #selector(togglePopover)
|
||||
}
|
||||
|
||||
self.popover = NSPopover()
|
||||
self.popover.contentSize = NSSize(width: 500, height: 500)
|
||||
self.popover.behavior = .transient
|
||||
self.popover.contentViewController = NSHostingController(rootView: MainView(taskTitle: "", taskDescription: "", listTask: [])
|
||||
)
|
||||
}
|
||||
|
||||
@objc func togglePopover() {
|
||||
|
||||
if let button = statusItem.button {
|
||||
if popover.isShown {
|
||||
self.popover.performClose(nil)
|
||||
} else {
|
||||
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
94
Luto/UI/MainView.swift
Normal file
94
Luto/UI/MainView.swift
Normal file
@ -0,0 +1,94 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Luto
|
||||
//
|
||||
// Created by Pierre Boulc'h on 04/07/2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
@State var taskTitle = ""
|
||||
@State var taskDescription = ""
|
||||
@State var listTask: [String] = []
|
||||
|
||||
@State var showAdditionnalFields = false
|
||||
@FocusState private var titleFieldInFocus: Bool
|
||||
@FocusState private var descriptionFieldInFocus: Bool
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Mes tâches")
|
||||
LazyVStack(alignment: .leading) {
|
||||
ForEach(Array(listTask.enumerated()), id: \.offset) { index, task in
|
||||
HStack {
|
||||
Text("\(task)")
|
||||
.padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 8))
|
||||
Spacer()
|
||||
Button(action: {
|
||||
removeTask(index: index)
|
||||
}, label: {
|
||||
Image(systemName: "trash")
|
||||
}).buttonStyle(PlainButtonStyle()).padding(12)
|
||||
.foregroundColor(.accentColor)
|
||||
}.border(width: 5, edges: [.leading], color: .accentColor)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
HStack {
|
||||
TextField("Titre de la tâche ...", text: $taskTitle).focused($titleFieldInFocus).onChange(of: titleFieldInFocus) { isFocused in
|
||||
if !isFocused {
|
||||
withAnimation {
|
||||
showAdditionnalFields = true
|
||||
}
|
||||
}
|
||||
}.onSubmit {
|
||||
addTask()
|
||||
}
|
||||
Button(action: {
|
||||
addTask()
|
||||
}, label: {
|
||||
Image(systemName: "plus")
|
||||
}).buttonStyle(PlainButtonStyle()).padding(12)
|
||||
.background(Color.accentColor)
|
||||
.foregroundColor(.black)
|
||||
.cornerRadius(24)
|
||||
}.textFieldStyle(OvalTextFieldStyle())
|
||||
if showAdditionnalFields {
|
||||
TextField("Description", text: $taskDescription).focused($descriptionFieldInFocus)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(width: 500, height: 500)
|
||||
}
|
||||
|
||||
func addTask() {
|
||||
if !taskTitle.isEmpty {
|
||||
withAnimation {
|
||||
listTask.append(taskTitle)
|
||||
}
|
||||
taskTitle = ""
|
||||
}
|
||||
}
|
||||
|
||||
func removeTask(index: Int) {
|
||||
_ = withAnimation {
|
||||
listTask.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
init(taskTitle: String = "", taskDescription: String = "", listTask: [String]) {
|
||||
self.taskTitle = taskTitle
|
||||
self.taskDescription = taskDescription
|
||||
self.listTask = listTask
|
||||
}
|
||||
}
|
||||
|
||||
struct MainView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MainView(taskTitle: "", taskDescription: "", listTask: [])
|
||||
}
|
||||
}
|
||||
32
Luto/UI/Utils/TextFieldExtensions.swift
Normal file
32
Luto/UI/Utils/TextFieldExtensions.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// TextFieldExtensions.swift
|
||||
// Luto
|
||||
//
|
||||
// Created by Pierre Boulc'h on 06/07/2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct OvalTextFieldStyle: TextFieldStyle {
|
||||
func _body(configuration: TextField<Self._Label>) -> some View {
|
||||
configuration
|
||||
.textFieldStyle(.plain)
|
||||
.colorMultiply(.gray)
|
||||
.padding(10)
|
||||
.tint(.gray)
|
||||
.foregroundColor(.black)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
.shadow(color: .accentColor, radius: 2)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension NSTextField {
|
||||
open override var focusRingType: NSFocusRingType {
|
||||
get { .none }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
31
Luto/UI/Utils/ViewExtensions.swift
Normal file
31
Luto/UI/Utils/ViewExtensions.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// ViewExtensions.swift
|
||||
// Luto
|
||||
//
|
||||
// Created by Pierre Boulc'h on 12/07/2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func border(width: CGFloat, edges: [Edge], color: Color) -> some View {
|
||||
overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))
|
||||
}
|
||||
}
|
||||
|
||||
struct EdgeBorder: Shape {
|
||||
var width: CGFloat
|
||||
var edges: [Edge]
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
edges.map { edge -> Path in
|
||||
switch edge {
|
||||
case .top: return Path(.init(x: rect.minX, y: rect.minY, width: rect.width, height: width))
|
||||
case .bottom: return Path(.init(x: rect.minX, y: rect.maxY - width, width: rect.width, height: width))
|
||||
case .leading: return Path(.init(x: rect.minX, y: rect.minY, width: width, height: rect.height))
|
||||
case .trailing: return Path(.init(x: rect.maxX - width, y: rect.minY, width: width, height: rect.height))
|
||||
}
|
||||
}.reduce(into: Path()) { $0.addPath($1) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user