Skip to content

Commit 604a94f

Browse files
committed
WIP attempt a widget
1 parent 08a6b8b commit 604a94f

55 files changed

Lines changed: 1379 additions & 332 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MusicPractice.xcodeproj/project.pbxproj

Lines changed: 233 additions & 2 deletions
Large diffs are not rendered by default.

MusicPractice/Components/Elements/MPList.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct MPList<T: RandomAccessCollection, R: View>: View where T.Element: Hashabl
3131
VStack(spacing: Spacing.tiny) {
3232
body
3333
footer()
34-
.opacity(Opacity.VeryFaded)
34+
.opacity(Opacity.veryFaded)
3535
.asRowWrapper()
3636
}
3737
}

MusicPractice/Components/Elements/SectionTitle.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct SectionTitle: View {
2222
Text(text.uppercased())
2323
.withSmallFont()
2424
.fixedSize(horizontal: true, vertical: false)
25-
.opacity(Opacity.Faded)
25+
.opacity(Opacity.faded)
2626

2727
Unwrap(icon) { icon in
2828
Spacer()

MusicPractice/Components/Form/MPButton.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct MPButton: View {
2323
if let handler = self.onTap { handler() }
2424
}) {
2525
HStack {
26-
Unwrap(label) { Text($0) }
26+
if let label = label { Text(label) }
2727
if let img = iconImage { Icon(img) }
2828
}
2929
.padding(.vertical, Spacing.small)
@@ -35,7 +35,7 @@ struct MPButton: View {
3535
#Preview {
3636
func handler () { print("tapped!") }
3737

38-
return ModalView(description: "Testing a button") {
38+
return VStack {
3939
MPButton("Take a picture", icon: Icons.aperture, onTap: handler)
4040
MPButton("Press me") { handler() }
4141
MPButton(icon: Icons.repeat)

MusicPractice/Components/HeaderContent.swift

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,42 @@ struct HeaderContent: View {
1212
var title: String?
1313
var description: String
1414
var performance: Performance?
15-
15+
1616
var body: some View {
1717
VStack(alignment: .leading, spacing: Spacing.small) {
18-
Image(self.icon).padding(.bottom, Spacing.small)
19-
Unwrap(self.title) { t in Text(t).opacity(Opacity.VeryFaded) }
18+
Image(icon).padding(.bottom, Spacing.small)
19+
Unwrap(self.title) { t in Text(t).opacity(Opacity.veryFaded) }
2020
Text(self.description).fixedSize(horizontal: false, vertical: true)
2121
}
2222
.frame(maxWidth: .infinity, alignment: .leading)
23-
.foregroundColor(self.fgColor)
23+
.foregroundColor(fgColor)
2424
}
25-
26-
var icon: String {
27-
let color = performance != nil ? "White" : "Blue"
28-
return "\(color)\(performance ?? Performance.Good)"
25+
26+
var icon: ImageResource {
27+
switch performance {
28+
case .good:
29+
.whiteGood
30+
case .bad:
31+
.whiteBad
32+
case .meh:
33+
.whiteMeh
34+
default:
35+
.blueGood
36+
}
2937
}
30-
38+
3139
var fgColor: Color {
3240
if performance != nil { return Color.white }
33-
41+
3442
return Colors.primary
3543
}
3644
}
3745

3846
struct HeaderContent_Previews: PreviewProvider {
39-
static var previews: some View {
40-
HeaderContent(
41-
title: "Hello",
42-
description: "World"
43-
).withDefaultStyles()
44-
}
47+
static var previews: some View {
48+
HeaderContent(
49+
title: "Hello",
50+
description: "World"
51+
).withDefaultStyles()
52+
}
4553
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// ActivityPracticingPill.swift
3+
// MusicPracticeWidgetExtensionExtension
4+
//
5+
// Created by Sam Garson on 24/02/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ActivityProgressBar: View {
11+
var practice: InProgressPractice
12+
var secondsPast: Double
13+
14+
var body: some View {
15+
HStack(alignment: .firstTextBaseline) {
16+
ProgressView(
17+
timerInterval: practice.startedAt...practice.finishedAt,
18+
countsDown: false
19+
)
20+
.progressViewStyle(TickProgressStyle())
21+
ActivityCountdownText(practice: practice)
22+
.font(Fonts.mediumBold)
23+
}
24+
}
25+
}
26+
27+
struct ActivityCountdownText: View {
28+
// var secondsPast: Double
29+
var practice: InProgressPractice
30+
31+
var body: some View {
32+
// Text(formatForCountdown(secondsPast))
33+
// .contentTransition(.numericText())
34+
Text(
35+
timerInterval: practice.finishedAt...practice.startedAt,
36+
countsDown: false
37+
)
38+
.frame(minWidth: 55)
39+
}
40+
}
41+
42+
struct ActivityPracticingPill: View {
43+
var body: some View {
44+
Text("Practicing")
45+
.withSmallFont()
46+
.textCase(.uppercase)
47+
.padding(.vertical, 6)
48+
.padding(.horizontal, 9)
49+
.lineLimit(1)
50+
.scaledToFit()
51+
.minimumScaleFactor(0.6)
52+
.background(.white.opacity(0.2), in: Capsule())
53+
}
54+
}
55+
56+
struct ActivityIcon: View {
57+
var body: some View {
58+
Image(.whiteGood)
59+
.resizable()
60+
.aspectRatio(contentMode: .fit)
61+
}
62+
}
63+
64+
struct ActivityTitle: View {
65+
var text: String
66+
67+
var body: some View {
68+
Text(text)
69+
.font(Fonts.large)
70+
.lineLimit(2)
71+
.minimumScaleFactor(0.7)
72+
}
73+
}
74+
75+
#Preview {
76+
ActivityPracticingPill()
77+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// ActivityExpandedContent.swift
3+
// MusicPractice
4+
//
5+
// Created by Sam Garson on 24/02/2025.
6+
//
7+
8+
import SwiftUI
9+
import WidgetKit
10+
11+
@DynamicIslandExpandedContentBuilder
12+
func activityDetailExpandedContent(
13+
practice: InProgressPractice,
14+
secondsPast: Double
15+
) -> DynamicIslandExpandedContent<some View> {
16+
DynamicIslandExpandedRegion(.leading) {
17+
ActivityPracticingPill()
18+
}
19+
DynamicIslandExpandedRegion(.trailing) {
20+
HStack {
21+
ActivityIcon()
22+
.frame(width: 20, height: 20)
23+
}.padding(.trailing, Spacing.small)
24+
}
25+
DynamicIslandExpandedRegion(.bottom) {
26+
VStack(alignment: .leading, spacing: Spacing.tiny) {
27+
Spacer()
28+
ActivityTitle(text: practice.practiceable.title)
29+
30+
ActivityProgressBar(practice: practice, secondsPast: secondsPast)
31+
Spacer()
32+
}
33+
}
34+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// ActivityWidget.swift
3+
// MusicPractice
4+
//
5+
// Created by Sam Garson on 24/02/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
struct ActivityWidget : View {
11+
var practice: InProgressPractice
12+
var secondsPast: Double
13+
14+
var body: some View {
15+
VStack(alignment: .leading, spacing: Spacing.tiny) {
16+
HStack {
17+
ActivityPracticingPill()
18+
Spacer()
19+
ActivityIcon().frame(width: 20, height: 20)
20+
}.opacity(Opacity.slightlyFaded)
21+
22+
ActivityTitle(text: practice.practiceable.title)
23+
24+
Spacer()
25+
26+
ActivityProgressBar(
27+
practice: practice,
28+
secondsPast: secondsPast
29+
)
30+
}
31+
32+
.padding(Spacing.small)
33+
.background(Colors.success)
34+
.foregroundStyle(.white)
35+
.withDefaultStyles()
36+
}
37+
}
38+
39+
#Preview {
40+
ActivityWidget(
41+
practice: .init(
42+
practiceable: .interval(.M6),
43+
startedAt: .now,
44+
minutesToPractice: 2
45+
),
46+
secondsPast: 60
47+
)
48+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// Countdown.swift
3+
// MusicPracticeWidgetExtensionExtension
4+
//
5+
// Created by Sam Garson on 23/02/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
struct TickProgressStyle: ProgressViewStyle {
11+
func makeBody(configuration: Configuration) -> some View {
12+
return ProgressView(configuration)
13+
.background(.white)
14+
.tint(.white)
15+
.cornerRadius(5)
16+
}
17+
}
18+
19+
20+
struct HorizontalCountdown: View {
21+
var totalTicks: Int
22+
var progress: Double
23+
24+
static let height: CGFloat = 12
25+
26+
var body: some View {
27+
HStack(alignment: .bottom, spacing: 2) {
28+
ForEach(ticks) { tick in
29+
RoundedRectangle(cornerRadius: 1)
30+
.fill(.white)
31+
.frame(width: 2, height: tick.height)
32+
.opacity(tick.opacity)
33+
.allowsHitTesting(false)
34+
}
35+
}
36+
}
37+
38+
private var ticks: [Tick] {
39+
(0..<totalTicks).map { i in
40+
Tick(index: i, totalTicks: totalTicks, progress: progress)
41+
}
42+
}
43+
44+
private struct Tick: Identifiable {
45+
var id: Int { index }
46+
var index: Int
47+
var totalTicks: Int
48+
var progress: Double
49+
50+
var on: Bool {
51+
index <= Int(Double(totalTicks) * progress)
52+
}
53+
54+
var height: CGFloat {
55+
on ? HorizontalCountdown.height : 8
56+
}
57+
58+
var opacity: Double {
59+
on ? 1 : 0.3
60+
}
61+
}
62+
}
63+
64+
#Preview {
65+
VStack {
66+
HorizontalCountdown(totalTicks: 80, progress: 0.25)
67+
ProgressView(timerInterval: Date()...Date().addingTimeInterval(60), countsDown: false)
68+
ProgressView(
69+
timerInterval: Date()...Date().addingTimeInterval(60),
70+
countsDown: false,
71+
label: { EmptyView() },
72+
currentValueLabel: { EmptyView() }
73+
)
74+
.progressViewStyle(TickProgressStyle())
75+
}.padding().background(Colors.success)
76+
}

0 commit comments

Comments
 (0)