Fix widget issues and add subscription bypass toggle
Widget fixes: - Fix App Group ID mismatch in iOS app entitlements (was group.com.tt.ifeel.ifeelDebug, now group.com.tt.ifeelDebug) - Fix date bug where missing entries all showed same date - Add sample data preview for widget picker (shows realistic mood data) - Add widgetDisplayName to Mood enum for widget localization - Update Mood Vote widget preview to show post-vote state - Attempt to fix interactive widget buttons (openAppWhenRun: false) Developer improvements: - Add IAPManager.bypassSubscription toggle for testing without subscription 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,18 +30,18 @@ class WatchTimelineView: Identifiable {
|
||||
struct TimeLineCreator {
|
||||
static func createViews(daysBack: Int) -> [WatchTimelineView] {
|
||||
var timeLineView = [WatchTimelineView]()
|
||||
|
||||
|
||||
let latestDayToShow = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: UserDefaultsStore.getOnboarding())
|
||||
let dates = Array(0...daysBack).map({
|
||||
Calendar.current.date(byAdding: .day, value: -$0, to: latestDayToShow)!
|
||||
})
|
||||
|
||||
|
||||
for date in dates {
|
||||
let dayStart = Calendar.current.startOfDay(for: date)
|
||||
let dayEnd = Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: dayStart)!
|
||||
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
|
||||
let moodImages: MoodImagable.Type = UserDefaultsStore.moodMoodImagable()
|
||||
|
||||
|
||||
if let todayEntry = PersistenceController.shared.getData(startDate: dayStart, endDate: dayEnd, includedDays: []).first {
|
||||
timeLineView.append(WatchTimelineView(image: moodImages.icon(forMood: todayEntry.mood),
|
||||
graphic: moodImages.icon(forMood: todayEntry.mood),
|
||||
@@ -51,15 +51,39 @@ struct TimeLineCreator {
|
||||
} else {
|
||||
timeLineView.append(WatchTimelineView(image: moodImages.icon(forMood: .missing),
|
||||
graphic: moodImages.icon(forMood: .missing),
|
||||
date: Date(),
|
||||
date: dayStart,
|
||||
color: moodTint.color(forMood: .missing),
|
||||
secondaryColor: moodTint.secondary(forMood: .missing)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
timeLineView = timeLineView.sorted(by: { $0.date > $1.date })
|
||||
return timeLineView
|
||||
}
|
||||
|
||||
/// Creates sample preview data for widget picker - shows what widget looks like with mood data
|
||||
static func createSampleViews(count: Int) -> [WatchTimelineView] {
|
||||
var timeLineView = [WatchTimelineView]()
|
||||
let sampleMoods: [Mood] = [.great, .good, .average, .good, .great, .average, .bad, .good, .great, .good, .average]
|
||||
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
|
||||
let moodImages: MoodImagable.Type = UserDefaultsStore.moodMoodImagable()
|
||||
|
||||
for i in 0..<count {
|
||||
let date = Calendar.current.date(byAdding: .day, value: -i, to: Date())!
|
||||
let dayStart = Calendar.current.startOfDay(for: date)
|
||||
let mood = sampleMoods[i % sampleMoods.count]
|
||||
|
||||
timeLineView.append(WatchTimelineView(
|
||||
image: moodImages.icon(forMood: mood),
|
||||
graphic: moodImages.icon(forMood: mood),
|
||||
date: dayStart,
|
||||
color: moodTint.color(forMood: mood),
|
||||
secondaryColor: moodTint.secondary(forMood: mood)
|
||||
))
|
||||
}
|
||||
|
||||
return timeLineView
|
||||
}
|
||||
}
|
||||
|
||||
struct Provider: IntentTimelineProvider {
|
||||
@@ -67,18 +91,25 @@ struct Provider: IntentTimelineProvider {
|
||||
|
||||
/*
|
||||
placeholder for widget, no data
|
||||
gets redacted auto
|
||||
gets redacted auto - uses sample data for widget picker preview
|
||||
*/
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
return SimpleEntry(date: Date(),
|
||||
configuration: ConfigurationIntent(),
|
||||
timeLineViews: Array(TimeLineCreator.createViews(daysBack: 11).prefix(10)))
|
||||
timeLineViews: TimeLineCreator.createSampleViews(count: 10))
|
||||
}
|
||||
|
||||
|
||||
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
// Use sample data for widget picker preview, real data otherwise
|
||||
let timeLineViews: [WatchTimelineView]
|
||||
if context.isPreview {
|
||||
timeLineViews = TimeLineCreator.createSampleViews(count: 10)
|
||||
} else {
|
||||
timeLineViews = Array(TimeLineCreator.createViews(daysBack: 11).prefix(10))
|
||||
}
|
||||
let entry = SimpleEntry(date: Date(),
|
||||
configuration: ConfigurationIntent(),
|
||||
timeLineViews: Array(TimeLineCreator.createViews(daysBack: 11).prefix(10)))
|
||||
timeLineViews: timeLineViews)
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
@@ -144,12 +175,18 @@ struct FeelsWidgetEntryView : View {
|
||||
struct SmallWidgetView: View {
|
||||
var entry: Provider.Entry
|
||||
var timeLineView = [WatchTimelineView]()
|
||||
|
||||
|
||||
init(entry: Provider.Entry) {
|
||||
self.entry = entry
|
||||
timeLineView = [TimeLineCreator.createViews(daysBack: 2).first!]
|
||||
let realData = TimeLineCreator.createViews(daysBack: 2)
|
||||
// Check if we have any real mood data (not all missing)
|
||||
let hasRealData = realData.contains { view in
|
||||
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
|
||||
return view.color != moodTint.color(forMood: .missing)
|
||||
}
|
||||
timeLineView = hasRealData ? [realData.first!] : [TimeLineCreator.createSampleViews(count: 1).first!]
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(UIColor.secondarySystemBackground)
|
||||
@@ -169,25 +206,31 @@ struct SmallWidgetView: View {
|
||||
struct MediumWidgetView: View {
|
||||
var entry: Provider.Entry
|
||||
var timeLineView = [WatchTimelineView]()
|
||||
|
||||
|
||||
init(entry: Provider.Entry) {
|
||||
self.entry = entry
|
||||
timeLineView = Array(TimeLineCreator.createViews(daysBack: 6).prefix(5))
|
||||
let realData = Array(TimeLineCreator.createViews(daysBack: 6).prefix(5))
|
||||
// Check if we have any real mood data (not all missing)
|
||||
let hasRealData = realData.contains { view in
|
||||
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
|
||||
return view.color != moodTint.color(forMood: .missing)
|
||||
}
|
||||
timeLineView = hasRealData ? realData : TimeLineCreator.createSampleViews(count: 5)
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
|
||||
TimeHeaderView(startDate: timeLineView.first!.date, endDate: timeLineView.last!.date)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
|
||||
TimeBodyView(group: timeLineView)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
|
||||
.frame(minHeight: 0, maxHeight: 55)
|
||||
.padding()
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
@@ -196,41 +239,47 @@ struct MediumWidgetView: View {
|
||||
struct LargeWidgetView: View {
|
||||
var entry: Provider.Entry
|
||||
var timeLineView = [WatchTimelineView]()
|
||||
|
||||
|
||||
init(entry: Provider.Entry) {
|
||||
self.entry = entry
|
||||
timeLineView = Array(TimeLineCreator.createViews(daysBack: 11).prefix(10))
|
||||
let realData = Array(TimeLineCreator.createViews(daysBack: 11).prefix(10))
|
||||
// Check if we have any real mood data (not all missing)
|
||||
let hasRealData = realData.contains { view in
|
||||
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
|
||||
return view.color != moodTint.color(forMood: .missing)
|
||||
}
|
||||
timeLineView = hasRealData ? realData : TimeLineCreator.createSampleViews(count: 10)
|
||||
}
|
||||
|
||||
|
||||
var firstGroup: ([WatchTimelineView], String) {
|
||||
return (Array(self.timeLineView.prefix(5)), UUID().uuidString)
|
||||
}
|
||||
|
||||
|
||||
var secondGroup: ([WatchTimelineView], String) {
|
||||
return (Array(self.timeLineView.suffix(5)), UUID().uuidString)
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
|
||||
ForEach([firstGroup, secondGroup], id: \.1) { group in
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
|
||||
TimeHeaderView(startDate: group.0.first!.date, endDate: group.0.last!.date)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
|
||||
TimeBodyView(group: group.0)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
|
||||
.frame(minHeight: 0, maxHeight: 55)
|
||||
.padding()
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
@@ -255,12 +304,18 @@ struct FeelsGraphicWidgetEntryView : View {
|
||||
struct SmallGraphicWidgetView: View {
|
||||
var entry: Provider.Entry
|
||||
var timeLineView: [WatchTimelineView]
|
||||
|
||||
|
||||
init(entry: Provider.Entry) {
|
||||
self.entry = entry
|
||||
timeLineView = TimeLineCreator.createViews(daysBack: 2)
|
||||
let realData = TimeLineCreator.createViews(daysBack: 2)
|
||||
// Check if we have any real mood data (not all missing)
|
||||
let hasRealData = realData.contains { view in
|
||||
let moodTint: MoodTintable.Type = UserDefaultsStore.moodTintable()
|
||||
return view.color != moodTint.color(forMood: .missing)
|
||||
}
|
||||
timeLineView = hasRealData ? realData : TimeLineCreator.createSampleViews(count: 2)
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
if let first = timeLineView.first {
|
||||
IconView(iconViewModel: IconViewModel(backgroundImage: first.graphic,
|
||||
|
||||
Reference in New Issue
Block a user