Skip to content

Commit

Permalink
Merge pull request #116 from Team-B1ND/feature/widget
Browse files Browse the repository at this point in the history
feat: Create meal widget
  • Loading branch information
hhhello0507 authored Aug 13, 2024
2 parents be7536e + f2ab092 commit 145a999
Show file tree
Hide file tree
Showing 12 changed files with 404 additions and 15 deletions.
26 changes: 25 additions & 1 deletion Projects/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,31 @@ let project = Project(
dependencies: [
.project(target: "Feature", path: .relativeToRoot("Projects/Feature")),
.project(target: "Repository", path: .relativeToRoot("Projects/Data")),
.project(target: "DIContainer", path: .relativeToRoot("Projects/DIContainer"))
.project(target: "DIContainer", path: .relativeToRoot("Projects/DIContainer")),
.target(name: "DodamDodamWidget")
]
),
.target(
name: "DodamDodamWidget",
destinations: [.iPhone],
product: .appExtension,
bundleId: "com.b1nd.dodam.student.WidgetExtension",
deploymentTargets: .iOS("15.0"),
infoPlist: .extendingDefault(with: [
"CFBundleDisplayName": "$(PRODUCT_NAME)",
"NSExtension": [
"NSExtensionPointIdentifier": "com.apple.widgetkit-extension",
]
]),
sources: ["iOS-Widget/Source/**"],
resources: ["iOS-Widget/Resource/**"],
scripts: [.swiftLint],
dependencies: [
.project(target: "Domain", path: .relativeToRoot("Projects/Domain")),
.project(target: "Repository", path: .relativeToRoot("Projects/Data")),
.project(target: "DIContainer", path: .relativeToRoot("Projects/DIContainer")),
.project(target: "Shared", path: .relativeToRoot("Projects/Shared")),
.external(name: "DDS")
]
)
]
Expand Down
25 changes: 25 additions & 0 deletions Projects/App/iOS-Widget/Source/AppMainWidget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// AppMainWidget.swift
// ProjectDescriptionHelpers
//
// Created by hhhello0507 on 7/23/24.
//

import Foundation
import WidgetKit
import SwiftUI
import DDS
import DIContainer

@main
struct AppMainWidget: WidgetBundle {

init() {
Pretendard.register()
DependencyProvider.shared.register()
}

var body: some Widget {
DodamMealWidget()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// DataSourceAssembly.swift
// DodamDodam
//
// Created by Mercen on 3/28/24.
//

import Swinject
import DataSource
import Network
import Repository

struct DataSourceAssembly: Assembly {

func assemble(container: Container) {
container.register(MealDataSource.self) {
.init(remote: $0.resolve(MealRemote.self)!)
}.inObjectScope(.container)
}
}
18 changes: 18 additions & 0 deletions Projects/App/iOS-Widget/Source/DI/Assembly/RemoteAssembly.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// RemoteAssembly.swift
// DodamDodam
//
// Created by Mercen on 3/28/24.
//

import Swinject
import Network

struct RemoteAssembly: Assembly {

func assemble(container: Container) {
container.register(MealRemote.self) { _ in
.init()
}.inObjectScope(.container)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// RepositoryAssembly.swift
// DodamDodam
//
// Created by Mercen on 3/28/24.
//

import Swinject
import Repository
import DataSource
import Domain

struct RepositoryAssembly: Assembly {

func assemble(container: Container) {
container.register((any MealRepository).self) {
MealRepositoryImpl(dataSource: $0.resolve(MealDataSource.self)!)
}.inObjectScope(.container)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// DependencyProvider.swift
// DodamDodam
//
// Created by Mercen on 3/13/24.
//

import Swinject
import DIContainer

public extension DependencyProvider {

func register() {
Container.loggingFunction = nil
_ = Assembler(
[
DataSourceAssembly(),
RemoteAssembly(),
RepositoryAssembly()
],
container: self.container
)
}
}
27 changes: 27 additions & 0 deletions Projects/App/iOS-Widget/Source/Entry/MealEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// MealEntry.swift
// DodamDodam
//
// Created by hhhello0507 on 7/23/24.
//

import WidgetKit
import Domain

struct MealEntry: TimelineEntry {
let date: Date
let meal: MealResponse
}

extension MealEntry {
static let empty = MealEntry(
date: .now,
meal: MealResponse(
exists: true,
date: .now,
breakfast: nil,
lunch: nil,
dinner: nil
)
)
}
90 changes: 90 additions & 0 deletions Projects/App/iOS-Widget/Source/Provider/MealProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// DodamMealProvider.swift
// DodamDodam
//
// Created by hhhello0507 on 7/23/24.
//

import WidgetKit
import Domain
import Shared
import DIContainer

struct MealProvider: TimelineProvider {

@Inject var mealRepository: any MealRepository
func placeholder(in context: Context) -> MealEntry {

let meal = Meal(
details: [
.init(name: "퀴노아녹두죽", allergies: []),
.init(name: "채소샐러드", allergies: []),
.init(name: "우자드레싱", allergies: []),
.init(name: "깍두기", allergies: []),
.init(name: "초코첵스시리얼+우ㅁㅁㅁㅁㅁ유", allergies: []),
.init(name: "초코크로와상", allergies: [])
],
calorie: 941
)
let entry = MealEntry(
date: .now,
meal: MealResponse(
exists: true,
date: .now,
breakfast: meal,
lunch: meal,
dinner: meal
)
)
return entry
}

func getSnapshot(in context: Context, completion: @escaping (MealEntry) -> Void) {
Task {
var currentDate = Date.now
if getDate(.hour, date: currentDate) >= 20 {
currentDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!
}
do {
let year = getDate(.year, date: currentDate)
let month = getDate(.month, date: currentDate)
let day = getDate(.day, date: currentDate)
let request = FetchMealRequest(year: year, month: month, day: day)
let meal = try await mealRepository.fetchMeal(request)
let entry = MealEntry(
date: currentDate,
meal: meal
)
completion(entry)
} catch {
let entry = MealEntry.empty
completion(entry)
}
}
}

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) ?? .init()
Task {
var currentDate = Date()
// 오후 8시가 지나면 다음날로
if getDate(.hour, date: currentDate) >= 20 {
currentDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!
}

do {
let meal = try await mealRepository.fetchMeal(.init(year: getDate(.year, date: currentDate), month: getDate(.month, date: currentDate), day: getDate(.day, date: currentDate)))
let entry = MealEntry(
date: currentDate,
meal: meal
)
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
} catch {
let entry = MealEntry.empty
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
}
}
129 changes: 129 additions & 0 deletions Projects/App/iOS-Widget/Source/Widget/DodamMealWidget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// DodamMealWidget.swift
// DodamDodam
//
// Created by hhhello0507 on 7/23/24.
//

import SwiftUI
import WidgetKit
import Shared
import DDS
import Domain

struct DodamMealWidget: Widget {

@Environment(\.widgetFamily) private var widgetFamily

private let widgetFamilyList: [WidgetFamily] = if #available(iOSApplicationExtension 16.0, *) {
[.systemSmall, .systemMedium, .accessoryRectangular, .accessoryCircular]
} else {
[.systemSmall, .systemMedium]
}

var body: some WidgetConfiguration {
StaticConfiguration(
kind: "DodamMealWidget",
provider: MealProvider()
) { entry in
SmallDodamMealWidget(entry: entry)
}
.configurationDisplayName("급식")
.description("아침, 점심, 저녁 위젯으로 빠르고 쉽게 확인해요")
.contentMarginsDisabled()
.supportedFamilies(widgetFamilyList)
}
}

struct SmallDodamMealWidget: View {

@State private var selection = 0

private let entry: MealProvider.Entry

init(entry: MealProvider.Entry) {
self.entry = entry
Pretendard.register()
}

var body: some View {
Group {
if #available(iOSApplicationExtension 17.0, *) {
label(meal: entry.meal)
.containerBackground(for: .widget) {
Dodam.color(DodamColor.Background.neutral)
}
} else {
label(meal: entry.meal)
}
}
.padding(8)
}

@ViewBuilder
private func label(meal: MealResponse) -> some View {
let idx = switch (getDate(.hour, date: .now), getDate(.minute, date: .now)) {
// 아침: ~ 8:20
case (0...8, _), (8, ..<20): 0
// 점심: 8:21 ~ 13:30
case (8, 21...60), (8...13, _), (13, 0..<30): 1
// 저녁: 13:31 ~ 19:10
case (13, 0...30), (13...19, _), (19, 0..<10): 2
default: -1
}
let (tag, meal): (String, Meal?) = switch idx {
case 0: ("아침", meal.breakfast)
case 1: ("점심", meal.lunch)
case 2: ("저녁", meal.dinner)
default: ("", nil)
}
content(tag: tag, meal: meal)
}

@ViewBuilder
private func content(tag: String, meal: Meal?) -> some View {
VStack(spacing: 4) {
HStack {
Text(tag)
.foreground(DodamColor.Static.white)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(DodamColor.Primary.normal)
.clipShape(.large)
.font(.footnote)
Spacer()
if let meal {
Text("\(Int(meal.calorie.rounded()))Kcal")
.font(.caption)
.foreground(DodamColor.Label.alternative)
}
}
VStack(alignment: .leading, spacing: 0) {
if let meal {
ForEach(meal.details, id: \.self) {
Text($0.name)
.lineLimit(1)
.truncationMode(.tail)
.font(.caption)
.foreground(DodamColor.Label.normal)
.frame(maxWidth: .infinity, alignment: .leading)
}
} else {
Text("급식이 없어요")
.caption1(.medium)
.foreground(DodamColor.Label.normal)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
}
.padding(8)
.frame(maxHeight: .infinity)
.background(DodamColor.Background.normal)
.clipShape(.large)
}
.background(DodamColor.Background.neutral)
}
}

#Preview {
SmallDodamMealWidget(entry: .empty)
}
Loading

0 comments on commit 145a999

Please sign in to comment.