Skip to content

Commit

Permalink
Feat: Widget 기능 추가
Browse files Browse the repository at this point in the history
commit 8381a3b
Author: doyeonKim <[email protected]>
Date:   Fri Oct 25 16:35:12 2024 +0900

    feat: 앱종료시 딥링크 동작시키는 기능 구현

commit 0f845f2
Author: doyeonKim <[email protected]>
Date:   Wed Oct 23 17:29:27 2024 +0900

    feat: SecondWidget UI 구현

commit b697610
Author: siwonLee <[email protected]>
Date:   Wed Oct 23 17:12:47 2024 +0900

    feat: Main Widget UI 구현

commit e91cad6
Author: doyeonKim <[email protected]>
Date:   Wed Oct 23 11:33:26 2024 +0900

    feat: widget 기본 세팅

    - Entry, provider 설정

commit af8b254
Author: doyeonKim <[email protected]>
Date:   Thu Oct 17 00:39:06 2024 +0900

    feat: widget target 추가
  • Loading branch information
FirstDo committed Oct 25, 2024
1 parent 447ec1d commit 633c493
Show file tree
Hide file tree
Showing 30 changed files with 477 additions and 12 deletions.
21 changes: 20 additions & 1 deletion Projects/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ let project = Project(
]
),
targets: [
.init(
name: "WidgetExtension",
platform: .iOS,
product: .appExtension,
bundleId: "com.yapp.moneymong.WidgetExtension",
infoPlist: .extendingDefault(with: [
"CFBundleDisplayName": "$(PRODUCT_NAME)",
"NSExtension": [
"NSExtensionPointIdentifier": "com.apple.widgetkit-extension",
],
]),
sources: "WidgetExtension/Sources/**",
resources: "WidgetExtension/Resources/**",
entitlements: "WidgetExtension/Resources/WidgetExtension.entitlements",
dependencies: [
.project(target: "DesignSystem", path: .relativeToRoot("Projects/Shared/DesignSystem"))
]
),
Target(
name: "Moneymong",
platform: .iOS,
Expand Down Expand Up @@ -63,7 +81,8 @@ let project = Project(
entitlements: "Resources/App.entitlements",
dependencies: [
.project(target: "SignFeature", path: .relativeToRoot("Projects/Feature/Sign")),
.project(target: "MainFeature", path: .relativeToRoot("Projects/Feature/Main"))
.project(target: "MainFeature", path: .relativeToRoot("Projects/Feature/Main")),
.target(name: "WidgetExtension")
],
settings: .settings(
base: .init()
Expand Down
4 changes: 4 additions & 0 deletions Projects/App/Resources/App.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
<array>
<string>Default</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.moneymong</string>
</array>
</dict>
</plist>
1 change: 0 additions & 1 deletion Projects/App/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
//URLSessionProxyDelegate.enableAutomaticRegistration()
FirebaseManager.shared.initSDK()
KakaoAuthManager.shared.initSDK()
return true
Expand Down
9 changes: 8 additions & 1 deletion Projects/App/Sources/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

self.appCoordinator = AppCoordinator(navigationController: navigationController)
appCoordinator?.start(animated: false)

self.scene(scene, openURLContexts: connectionOptions.urlContexts)
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
KakaoAuthManager.shared.openURL(url)

if url.absoluteString.contains("widget://") {
DeepLinkManager.setDestination(url.absoluteString)
} else {
KakaoAuthManager.shared.openURL(url)
}
}

func sceneDidDisconnect(_ scene: UIScene) {}
Expand Down
29 changes: 29 additions & 0 deletions Projects/App/WidgetExtension/Resources/WidgetExtension-Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.applesignin</key>
<key>com.apple.security.application-groups</key>
<array>
<string>Default</string>
<string>group.moneymong</string>
</array>
</dict>
</plist>
50 changes: 50 additions & 0 deletions Projects/App/WidgetExtension/Sources/Model/AgencyEntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import WidgetKit

struct AgencyEntry: TimelineEntry {
let date: Date
let name: String
let amount: Int
}

struct AgencyProvider: TimelineProvider {
func placeholder(in context: Context) -> AgencyEntry {
AgencyEntry(
date: Date(),
name: "머니몽 대학",
amount: 10000
)
}

func getSnapshot(in context: Context, completion: @escaping (AgencyEntry) -> ()) {
let entry = AgencyEntry(
date: Date(),
name: "머니몽 대학",
amount: 10000
)
completion(entry)
}

func getTimeline(in context: Context, completion: @escaping (Timeline<AgencyEntry>) -> ()) {
let entry: AgencyEntry
if let dic = UserDefaults(suiteName: "group.moneymong")?.dictionary(forKey: "test"),
let name = dic["name"] as? String,
let amount = dic["total"] as? Int {

entry = AgencyEntry(
date: .now,
name: name,
amount: amount
)

} else {
entry = AgencyEntry(
date: .now,
name: "",
amount: 0
)
}
let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: .now)
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate!))
completion(timeline)
}
}
21 changes: 21 additions & 0 deletions Projects/App/WidgetExtension/Sources/Model/OCREntry.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import WidgetKit

struct OCREntry: TimelineEntry {
let date: Date
}

struct OCRProvider: TimelineProvider {
func placeholder(in context: Context) -> OCREntry {
OCREntry(date: Date())
}

func getSnapshot(in context: Context, completion: @escaping (OCREntry) -> ()) {
let entry = OCREntry(date: Date())
completion(entry)
}

func getTimeline(in context: Context, completion: @escaping (Timeline<OCREntry>) -> ()) {
let timeline = Timeline(entries: [OCREntry(date: .now)], policy: .never)
completion(timeline)
}
}
80 changes: 80 additions & 0 deletions Projects/App/WidgetExtension/Sources/Widgets/MainWidget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import WidgetKit
import SwiftUI
import DesignSystem

struct MainWidget: Widget {
private let kind: String = "MainWidget"

var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: AgencyProvider()) { entry in
MainWidgetEntryView(entry: entry)
}
.supportedFamilies([.systemMedium])
.configurationDisplayName("회비 내역 등록")
.description("등록할 회비 내역을 바로 입력할 수 있어요")
}
}

struct MainWidgetEntryView: View {
var entry: AgencyProvider.Entry


var body: some View {
VStack {
Link(destination: URL(string: "widget://CreateLedger")!) {
HStack(alignment: .center) {
Text("\(entry.name)에 오늘 사용한 금액 입력")
.bold()
.font(.system(size: 16))
.foregroundStyle(Color(uiColor: Colors.Gray._6))
.frame(height: 24)
.frame(maxWidth: .infinity)
.padding(.leading, 16)
.padding(.vertical, 18)
Image(uiImage: Images.mongCoin!)
.padding(.trailing, 16)
}
.background(Color.white)
.cornerRadius(12)
}

Spacer()
HStack {
Link(destination: URL(string: "widget://OCR")!) {
HStack {
Spacer()
Text("영수증 스캔")
.bold()
.font(.system(size: 16))
.foregroundStyle(Color(uiColor: Colors.Gray._5))
Spacer()
}
}

Divider()
.background(Color(uiColor: Colors.Gray._5))
Link(destination: URL(string: "widget://LedgerDetail")!) {
HStack {
Spacer()
Text("회비 내역 확인")
.bold()
.font(.system(size: 16))
.foregroundStyle(Color(uiColor: Colors.Gray._5))
Spacer()
}
}
}
.frame(height: 24)
Spacer()
}
.containerBackground(for: .widget) {
Color(uiColor: Colors.Gray._1)
}
}
}

#Preview(as: .systemMedium, widget: {
MainWidget()
}, timeline: {
AgencyEntry(date: .now, name: "머니몽", amount: 1000)
})
40 changes: 40 additions & 0 deletions Projects/App/WidgetExtension/Sources/Widgets/OCRWidget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import WidgetKit
import SwiftUI

import DesignSystem

struct OCRWidget: Widget {
private let kind: String = "OCRWidget"

var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: OCRProvider()) { entry in
OCRWidgetEntryView()
}
.supportedFamilies([.systemSmall])
.configurationDisplayName("회비 내역 스캔")
.description("영수증 스캔으로 빠르게 회비 내역을 기록해보세요")
}
}

struct OCRWidgetEntryView: View {
var body: some View {
VStack {
if let image = Images.scanCircle {
Image(uiImage: image)
.resizable()
.frame(width: 80, height: 80)
}

Spacer()

Text("영수증 스캔하기")
.foregroundColor(Color(uiColor: Colors.Gray._8))
.font(.system(size: 16, weight: .bold))
}
.padding(.vertical, 20)
.widgetURL(URL(string: "widget://OCR"))
.containerBackground(for: .widget) {
Color(uiColor: Colors.Gray._1)
}
}
}
83 changes: 83 additions & 0 deletions Projects/App/WidgetExtension/Sources/Widgets/SecondWidget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import WidgetKit
import SwiftUI

import DesignSystem

struct SecondWidget: Widget {
private let kind: String = "SecondWidget"

var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: AgencyProvider()) { entry in
SecondWidgetEntryView(entry: entry)
}
.supportedFamilies([.systemMedium])
.configurationDisplayName("내 장부")
.description("장부에 돈이 얼마 남앗는지 바로 확인할 수 있어요")
}
}

struct SecondWidgetEntryView: View {
var entry: AgencyProvider.Entry

var body: some View {
VStack {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading, spacing: 4) {
Text("\(entry.name)에 이만큼 남았어요")
.font(.system(size: 12, weight: .semibold))
.foregroundColor(Color(uiColor: Colors.Gray._6))
.frame(height: 18)

Text("\(entry.amount)")
.font(.system(size: 28, weight: .bold))
.foregroundColor(Color(uiColor: Colors.Gray._10))
.frame(height: 26)
}

Spacer()

Image(uiImage: Images.mongLedger!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 98)
.offset(y: -10)
}

HStack(spacing: 10) {
Link(destination: URL(string: "widget://OCR")!) {
Text("영수증 스캔")
.font(.system(size: 12, weight: .bold))
.foregroundColor(Color(uiColor: Colors.White._1))
.frame(height: 18)
.frame(maxWidth: .infinity)
.padding(10)
.background(Color(uiColor: Colors.Blue._4))
.cornerRadius(10)
}

Link(destination: URL(string: "widget://LedgerDetail")!) {
Text("회비 내역 확인")
.font(.system(size: 12, weight: .bold))
.foregroundColor(Color(uiColor: Colors.Blue._4))
.frame(height: 18)
.frame(maxWidth: .infinity)
.padding(10)
.background(Color(uiColor: Colors.Blue._1))
.cornerRadius(10)
}
}
}
.padding(.horizontal, 10)
.padding(.vertical, 20)
.containerBackground(for: .widget) {
Color(uiColor: Colors.Gray._1)
}
}
}

#Preview(as: .systemMedium) {
SecondWidget()
} timeline: {
AgencyEntry(date: .now, name: "머니몽", amount: 100000)
AgencyEntry(date: .now, name: "머니몽2", amount: 120000)
}
Loading

0 comments on commit 633c493

Please sign in to comment.