From f19f7236a385f8af3692e8b00b2c22926a00530c Mon Sep 17 00:00:00 2001 From: abdulsaheel Date: Fri, 30 Oct 2026 19:06:31 +0530 Subject: [PATCH] add pickleball to the activity picker a WHOOP import could bring in pickleball sessions, but the picker had no pickleball, so they showed as an unknown activity and couldn't be logged by hand. it sits with the racquet sports, with no calorie rate, like padel. apple health gets its own pickleball type; health connect has none, so it lands as tennis there. --- lib/health/health_export.dart | 6 ++++++ lib/l10n/app_ru.arb | 2 +- lib/ui2/activity/catalogue.dart | 4 ++++ lib/ui2/activity/summary.dart | 4 ++-- test/ui2_activity_test.dart | 3 ++- test/whoop_activity_type_test.dart | 10 ++++++++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/health/health_export.dart b/lib/health/health_export.dart index 88c0eff0c..4b1a6df92 100644 --- a/lib/health/health_export.dart +++ b/lib/health/health_export.dart @@ -1761,6 +1761,12 @@ HealthWorkoutActivityType healthActivityForType( case 'padel': case 'badminton': return HealthWorkoutActivityType.TENNIS; + case 'pickleball': + // iOS has its own type; Health Connect has none, so it files with the + // other racquet sports. + return ios + ? HealthWorkoutActivityType.PICKLEBALL + : HealthWorkoutActivityType.TENNIS; case 'basketball': return HealthWorkoutActivityType.BASKETBALL; case 'soccer': diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index c2c980120..f09070a69 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -5800,7 +5800,7 @@ "tabNutrition": "Питание", "tabWorkout": "Тренировка", "tabWellness": "Самочувствие", - "activityCatalogueName": "{key, select, kcardio{Кардио} kstrength{Силовые} ksports{Спорт} kathletics{Лёгкая атлетика} koutdoor{На природе} kmindbody{Тело и внимание} keveryday{Повседневные дела} kother{Другое} krunning{Бег} ktrailrunning{Трейлраннинг} kwalking{Ходьба} khiking{Пеший поход} kcycling{Велоспорт} kindoorbike{Велотренажёр} krowing{Гребля} kswimming{Плавание} kelliptical{Эллиптический тренажёр} kstairclimber{Степпер} ktreadmill{Беговая дорожка} kjumprope{Прыжки со скакалкой} kweighttraining{Силовая тренировка} kpowerlifting{Пауэрлифтинг} kbodyweight{С собственным весом} kcalisthenics{Калистеника} kkettlebell{Гиря} kcrossfit{CrossFit} khiit{ВИИТ} kcircuittraining{Круговая тренировка} kfunctional{Функциональная тренировка} kfootball{Футбол} kbasketball{Баскетбол} kcricket{Крикет} ktennis{Теннис} kbadminton{Бадминтон} ktabletennis{Настольный теннис} ksquash{Сквош} kpadel{Падел} kvolleyball{Волейбол} khockey{Хоккей} kbaseball{Бейсбол} krugby{Регби} kgolf{Гольф} kbowling{Боулинг} kboxing{Бокс} kmartialarts{Боевые искусства} kwrestling{Борьба} kclimbing{Скалолазание} ksprinting{Спринт} ktrackintervals{Интервалы на стадионе} kcrosscountry{Кросс} khurdles{Бег с барьерами} klongjump{Прыжки в длину} kshotput{Толкание ядра} kjavelin{Метание копья} kpolevault{Прыжки с шестом} kmountainbiking{Горный велосипед} kkayaking{Каякинг} ksurfing{Сёрфинг} kpaddleboard{Сапбординг} kskiing{Лыжи} ksnowboarding{Сноуборд} kskating{Катание на коньках} kskateboarding{Скейтбординг} khorseriding{Верховая езда} kyoga{Йога} kpilates{Пилатес} kstretching{Растяжка} kmobility{Подвижность суставов} ktaichi{Тайцзи} kbreathwork{Дыхательная практика} kmeditation{Медитация} khousework{Домашние дела} kgardening{Садовые работы} kdogwalking{Прогулка с собакой} kchildcare{Уход за ребёнком} kdiy{Ремонт своими руками} kshopping{Покупки} kstairs{Подъём по лестнице} kgeneralworkout{Общая тренировка} kdancing{Танцы} kintimacy{Интимная близость} kphysiotherapy{Физиотерапия} ksauna{Сауна} kcoldplunge{Погружение в холодную воду} other{{name}}}", + "activityCatalogueName": "{key, select, kcardio{Кардио} kstrength{Силовые} ksports{Спорт} kathletics{Лёгкая атлетика} koutdoor{На природе} kmindbody{Тело и внимание} keveryday{Повседневные дела} kother{Другое} krunning{Бег} ktrailrunning{Трейлраннинг} kwalking{Ходьба} khiking{Пеший поход} kcycling{Велоспорт} kindoorbike{Велотренажёр} krowing{Гребля} kswimming{Плавание} kelliptical{Эллиптический тренажёр} kstairclimber{Степпер} ktreadmill{Беговая дорожка} kjumprope{Прыжки со скакалкой} kweighttraining{Силовая тренировка} kpowerlifting{Пауэрлифтинг} kbodyweight{С собственным весом} kcalisthenics{Калистеника} kkettlebell{Гиря} kcrossfit{CrossFit} khiit{ВИИТ} kcircuittraining{Круговая тренировка} kfunctional{Функциональная тренировка} kfootball{Футбол} kbasketball{Баскетбол} kcricket{Крикет} ktennis{Теннис} kbadminton{Бадминтон} ktabletennis{Настольный теннис} ksquash{Сквош} kpadel{Падел} kpickleball{Пиклбол} kvolleyball{Волейбол} khockey{Хоккей} kbaseball{Бейсбол} krugby{Регби} kgolf{Гольф} kbowling{Боулинг} kboxing{Бокс} kmartialarts{Боевые искусства} kwrestling{Борьба} kclimbing{Скалолазание} ksprinting{Спринт} ktrackintervals{Интервалы на стадионе} kcrosscountry{Кросс} khurdles{Бег с барьерами} klongjump{Прыжки в длину} kshotput{Толкание ядра} kjavelin{Метание копья} kpolevault{Прыжки с шестом} kmountainbiking{Горный велосипед} kkayaking{Каякинг} ksurfing{Сёрфинг} kpaddleboard{Сапбординг} kskiing{Лыжи} ksnowboarding{Сноуборд} kskating{Катание на коньках} kskateboarding{Скейтбординг} khorseriding{Верховая езда} kyoga{Йога} kpilates{Пилатес} kstretching{Растяжка} kmobility{Подвижность суставов} ktaichi{Тайцзи} kbreathwork{Дыхательная практика} kmeditation{Медитация} khousework{Домашние дела} kgardening{Садовые работы} kdogwalking{Прогулка с собакой} kchildcare{Уход за ребёнком} kdiy{Ремонт своими руками} kshopping{Покупки} kstairs{Подъём по лестнице} kgeneralworkout{Общая тренировка} kdancing{Танцы} kintimacy{Интимная близость} kphysiotherapy{Физиотерапия} ksauna{Сауна} kcoldplunge{Погружение в холодную воду} other{{name}}}", "@activityCatalogueName": { "placeholders": { "key": { diff --git a/lib/ui2/activity/catalogue.dart b/lib/ui2/activity/catalogue.dart index 3a6e00927..bc47dbe52 100644 --- a/lib/ui2/activity/catalogue.dart +++ b/lib/ui2/activity/catalogue.dart @@ -171,6 +171,10 @@ const activityLibrary = [ // are different games, and borrowing either would be a stand-in. The // post-session estimate still works from heart rate. Activity('Padel', LucideIcons.volleyball, C.yellow, Track.duration, null), + // No MET for the same reason: the 2011 compendium predates pickleball as + // a listed activity, and paddleball or tennis doubles would be a guess. + Activity( + 'Pickleball', LucideIcons.volleyball, C.green, Track.duration, null), Activity('Volleyball', LucideIcons.volleyball, C.orange, Track.duration, 6.0), Activity('Hockey', LucideIcons.target, C.blue, Track.duration, 8.0), Activity('Baseball', LucideIcons.target, C.red, Track.duration, 5.0), diff --git a/lib/ui2/activity/summary.dart b/lib/ui2/activity/summary.dart index 7fd6d6c1d..7caf098a9 100644 --- a/lib/ui2/activity/summary.dart +++ b/lib/ui2/activity/summary.dart @@ -64,8 +64,8 @@ enum Arch { route, strength, interval, flow, laps, journey, match, basic } const _sports = { 'Football', 'Basketball', 'Cricket', 'Tennis', 'Badminton', 'Table tennis', - 'Squash', 'Padel', 'Volleyball', 'Hockey', 'Baseball', 'Rugby', 'Boxing', - 'Martial arts', 'Wrestling', + 'Squash', 'Padel', 'Pickleball', 'Volleyball', 'Hockey', 'Baseball', + 'Rugby', 'Boxing', 'Martial arts', 'Wrestling', }; const _laps = {'Swimming', 'Rowing'}; const _journey = { diff --git a/test/ui2_activity_test.dart b/test/ui2_activity_test.dart index e1721108c..3e083adf2 100644 --- a/test/ui2_activity_test.dart +++ b/test/ui2_activity_test.dart @@ -183,7 +183,8 @@ void main() { // saying it was told nothing, or a named sport the compendium has no // row for. Pinned by name, or the exception quietly becomes a habit. final unpriced = allActivities.where((a) => a.met == null).toList(); - expect(unpriced.map((a) => a.name), ['Padel', 'General workout']); + expect(unpriced.map((a) => a.name), + ['Padel', 'Pickleball', 'General workout']); }); test('padel, skateboarding and push-ups resolve by their stored keys', () { diff --git a/test/whoop_activity_type_test.dart b/test/whoop_activity_type_test.dart index 21960dd37..63d39e1ab 100644 --- a/test/whoop_activity_type_test.dart +++ b/test/whoop_activity_type_test.dart @@ -44,6 +44,16 @@ void main() { } }); + test('an imported pickleball session is a catalogue sport', () { + final key = WhoopImporter.activityTypeKey('Pickleball'); + expect(activityByName(key)?.name, 'Pickleball'); + expect(healthActivityForType(key, ios: true), + HealthWorkoutActivityType.PICKLEBALL); + // Health Connect has no pickleball type. + expect(healthActivityForType(key, ios: false), + HealthWorkoutActivityType.TENNIS); + }); + test('rows stored with the raw WHOOP name still map', () { // The importer never rewrites a session it already holds, so rows from // an earlier import keep the old spelling.