remove Expo project and all related files
Remove the entire Expo/React Native application: routes (app/), source code (src/), assets, iOS native build, config plugins, StoreKit config, npm dependencies, TypeScript/ESLint/Vitest configs, and Expo-specific documentation. The repository now contains only: admin-web, supabase, youtube-worker, tabatago-swift, docs, scripts, and CI/tooling configs.
This commit is contained in:
29
tabatago-swift/TabataGoWatch/Complications/Info.plist
Normal file
29
tabatago-swift/TabataGoWatch/Complications/Info.plist
Normal file
@@ -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>TabataGoWidget</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>
|
||||
@@ -0,0 +1,165 @@
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// ─── Timeline entry ───────────────────────────────────────────────────────────
|
||||
|
||||
struct TabataEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let streak: Int
|
||||
let lastWorkoutLabel: String // e.g. "Yesterday" or "2 days ago"
|
||||
}
|
||||
|
||||
// ─── Provider ─────────────────────────────────────────────────────────────────
|
||||
|
||||
struct TabataProvider: TimelineProvider {
|
||||
|
||||
private let sharedDefaults = UserDefaults(suiteName: "group.com.tabatago.app")
|
||||
|
||||
func placeholder(in context: Context) -> TabataEntry {
|
||||
TabataEntry(date: Date(), streak: 7, lastWorkoutLabel: "Today")
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (TabataEntry) -> Void) {
|
||||
completion(makeEntry())
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<TabataEntry>) -> Void) {
|
||||
let entry = makeEntry()
|
||||
// Refresh at midnight so the streak date label stays accurate
|
||||
let midnight = Calendar.current.startOfDay(for: Date().addingTimeInterval(86_400))
|
||||
completion(Timeline(entries: [entry], policy: .after(midnight)))
|
||||
}
|
||||
|
||||
private func makeEntry() -> TabataEntry {
|
||||
let streak = sharedDefaults?.integer(forKey: "streak") ?? 0
|
||||
let lastDate = sharedDefaults?.object(forKey: "lastWorkoutDate") as? Date
|
||||
let label = relativeLabel(for: lastDate)
|
||||
return TabataEntry(date: Date(), streak: streak, lastWorkoutLabel: label)
|
||||
}
|
||||
|
||||
private func relativeLabel(for date: Date?) -> String {
|
||||
guard let date else { return "Not started" }
|
||||
let days = Calendar.current.dateComponents([.day],
|
||||
from: Calendar.current.startOfDay(for: date),
|
||||
to: Calendar.current.startOfDay(for: Date())).day ?? 0
|
||||
switch days {
|
||||
case 0: return "Today"
|
||||
case 1: return "Yesterday"
|
||||
default: return "\(days) days ago"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Views ────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// `.accessoryCircular` — streak count inside an orange ring
|
||||
struct CircularComplicationView: View {
|
||||
let entry: TabataEntry
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
VStack(spacing: 0) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.system(size: 9, weight: .bold))
|
||||
.foregroundStyle(.orange)
|
||||
Text("\(entry.streak)")
|
||||
.font(.system(size: 18, weight: .black, design: .rounded))
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
}
|
||||
.widgetAccentable()
|
||||
}
|
||||
}
|
||||
|
||||
/// `.accessoryRectangular` — streak + last workout date
|
||||
struct RectangularComplicationView: View {
|
||||
let entry: TabataEntry
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundStyle(.orange)
|
||||
.font(.system(size: 10, weight: .bold))
|
||||
Text("\(entry.streak) day streak")
|
||||
.font(.system(size: 12, weight: .bold, design: .rounded))
|
||||
}
|
||||
Text(entry.lastWorkoutLabel)
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(.secondary)
|
||||
Text("Open TabataGo →")
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.widgetAccentable()
|
||||
}
|
||||
}
|
||||
|
||||
/// `.accessoryCorner` — tiny bolt + streak digit in the corner
|
||||
struct CornerComplicationView: View {
|
||||
let entry: TabataEntry
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
.widgetLabel("\(entry.streak) day streak")
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Widget definition ────────────────────────────────────────────────────────
|
||||
|
||||
struct TabataGoComplication: Widget {
|
||||
static let kind = "TabataGoComplication"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: Self.kind, provider: TabataProvider()) { entry in
|
||||
TabataComplicationEntryView(entry: entry)
|
||||
.containerBackground(.black, for: .widget)
|
||||
}
|
||||
.configurationDisplayName("TabataGo")
|
||||
.description("Your current workout streak.")
|
||||
.supportedFamilies([
|
||||
.accessoryCircular,
|
||||
.accessoryRectangular,
|
||||
.accessoryCorner
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
struct TabataComplicationEntryView: View {
|
||||
@Environment(\.widgetFamily) var family
|
||||
let entry: TabataEntry
|
||||
|
||||
var body: some View {
|
||||
switch family {
|
||||
case .accessoryCircular:
|
||||
CircularComplicationView(entry: entry)
|
||||
case .accessoryRectangular:
|
||||
RectangularComplicationView(entry: entry)
|
||||
case .accessoryCorner:
|
||||
CornerComplicationView(entry: entry)
|
||||
default:
|
||||
CircularComplicationView(entry: entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Previews ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#Preview("Circular", as: .accessoryCircular) {
|
||||
TabataGoComplication()
|
||||
} timeline: {
|
||||
TabataEntry(date: .now, streak: 7, lastWorkoutLabel: "Today")
|
||||
}
|
||||
|
||||
#Preview("Rectangular", as: .accessoryRectangular) {
|
||||
TabataGoComplication()
|
||||
} timeline: {
|
||||
TabataEntry(date: .now, streak: 7, lastWorkoutLabel: "Today")
|
||||
}
|
||||
Reference in New Issue
Block a user