Skip to content

Commit 172404b

Browse files
authored
feat: add wind direction display to both web and mobile apps (#38)
1 parent 67036f0 commit 172404b

18 files changed

Lines changed: 182 additions & 26 deletions

File tree

mobile/app.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
44
...config,
55
name: "beta.rocks",
66
slug: "beta-rocks",
7-
version: "0.4.0",
7+
version: "0.5.0",
88
orientation: "portrait",
99
icon: "./assets/icon.png",
1010
scheme: "betarocks",

mobile/app/crag/[slug].tsx

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
formatWindSpeed,
3636
convertPrecipitation,
3737
formatPrecipitation,
38+
getWindCardinal,
39+
getWindArrowRotation,
3840
} from "@/lib/units";
3941
import type { Report } from "@/types/api";
4042
import { FRICTION_RATINGS, RATING_COLORS, CATEGORY_COLORS } from "@/constants/config";
@@ -795,7 +797,8 @@ export default function CragDetailScreen() {
795797
<ConditionItem
796798
icon="flag-outline"
797799
label={t("dialog.wind", "Wind")}
798-
value={formatWindSpeed(convertWindSpeed(conditions.current.windSpeed_kph, "kmh", units.windSpeed), units.windSpeed)}
800+
value={`${formatWindSpeed(convertWindSpeed(conditions.current.windSpeed_kph, "kmh", units.windSpeed), units.windSpeed)}${conditions.current.windDirection != null ? ` ${getWindCardinal(conditions.current.windDirection)}` : ""}`}
801+
windDirection={conditions.current.windDirection}
799802
colors={colors}
800803
/>
801804
<ConditionItem
@@ -926,6 +929,7 @@ export default function CragDetailScreen() {
926929
const rc = getRatingColors(rl);
927930
const temp = h.temp_c ?? h.temperature_c;
928931
const wind = h.wind_kph ?? h.windSpeed_kph;
932+
const windDir = h.wind_direction;
929933
return (
930934
<View key={`good-${i}`} style={[styles.hourlyRow, {
931935
backgroundColor: rl === "Great" ? "rgba(34,197,94,0.08)" : "rgba(59,130,246,0.06)",
@@ -939,9 +943,14 @@ export default function CragDetailScreen() {
939943
{formatTemperature(convertTemperature(temp, "celsius", units.temperature), units.temperature, 0)}
940944
</Text>
941945
<Text style={[styles.hourlyValue, { color: colors.muted }]}>{h.humidity}%</Text>
942-
<Text style={[styles.hourlyValue, { color: colors.muted }]}>
943-
{formatWindSpeed(convertWindSpeed(wind, "kmh", units.windSpeed), units.windSpeed, 0)}
944-
</Text>
946+
<View style={{ flexDirection: "row", alignItems: "center" }}>
947+
<Text style={[styles.hourlyValue, { color: colors.muted }]}>
948+
{formatWindSpeed(convertWindSpeed(wind, "kmh", units.windSpeed), units.windSpeed, 0)}
949+
</Text>
950+
{windDir != null && (
951+
<Text style={[styles.hourlyValue, { color: colors.muted, transform: [{ rotate: `${getWindArrowRotation(windDir)}deg` }], marginLeft: 2 }]}></Text>
952+
)}
953+
</View>
945954
{rc && rl && (
946955
<View style={[styles.smallBadge, { backgroundColor: rc.bg, marginLeft: "auto" }]}>
947956
<Text style={[styles.smallBadgeText, { color: rc.text }]}>{t(`ratings.${rl!.toLowerCase()}`, rl)}</Text>
@@ -980,6 +989,9 @@ export default function CragDetailScreen() {
980989
</Text>
981990
<Ionicons name="flag-outline" size={12} color={colors.muted} />
982991
<Text style={[styles.forecastWind, { color: colors.muted }]}>{Math.round(wind)}</Text>
992+
{day.windDirectionDominant != null && (
993+
<Text style={[styles.forecastWind, { color: colors.muted, transform: [{ rotate: `${getWindArrowRotation(day.windDirectionDominant)}deg` }] }]}></Text>
994+
)}
983995
</View>
984996
</View>
985997
);
@@ -1136,6 +1148,7 @@ function HourlyTimeline({ hours, colors, units, t }: { hours: any[]; colors: (ty
11361148
const rowBg = rl === "Great" ? "rgba(34,197,94,0.06)" : rl === "Good" ? "rgba(59,130,246,0.04)" : "transparent";
11371149
const temp = h.temp_c ?? h.temperature_c;
11381150
const wind = h.wind_kph ?? h.windSpeed_kph;
1151+
const windDir = h.wind_direction;
11391152
return (
11401153
<View key={i} style={[styles.hourlyRow, { backgroundColor: rowBg }, i > 0 && { borderTopWidth: 1, borderTopColor: colors.border }]}>
11411154
<Text style={[styles.hourlyTime, { color: colors.text }]}>{fmtHour(h.time, units?.timeFormat || "24h")}</Text>
@@ -1144,9 +1157,14 @@ function HourlyTimeline({ hours, colors, units, t }: { hours: any[]; colors: (ty
11441157
{formatTemperature(convertTemperature(temp, "celsius", units.temperature), units.temperature, 0)}
11451158
</Text>
11461159
<Text style={[styles.hourlyValue, { color: colors.muted }]}>{h.humidity}%</Text>
1147-
<Text style={[styles.hourlyValue, { color: colors.muted }]}>
1148-
{formatWindSpeed(convertWindSpeed(wind, "kmh", units.windSpeed), units.windSpeed, 0)}
1149-
</Text>
1160+
<View style={{ flexDirection: "row", alignItems: "center" }}>
1161+
<Text style={[styles.hourlyValue, { color: colors.muted }]}>
1162+
{formatWindSpeed(convertWindSpeed(wind, "kmh", units.windSpeed), units.windSpeed, 0)}
1163+
</Text>
1164+
{windDir != null && (
1165+
<Text style={[styles.hourlyValue, { color: colors.muted, transform: [{ rotate: `${getWindArrowRotation(windDir)}deg` }], marginLeft: 2 }]}></Text>
1166+
)}
1167+
</View>
11501168
{rc && rl && (
11511169
<View style={[styles.smallBadge, { backgroundColor: rc.bg, marginLeft: "auto" }]}>
11521170
<Text style={[styles.smallBadgeText, { color: rc.text }]}>{t(`ratings.${rl!.toLowerCase()}`, rl)}</Text>
@@ -1159,12 +1177,17 @@ function HourlyTimeline({ hours, colors, units, t }: { hours: any[]; colors: (ty
11591177
);
11601178
}
11611179

1162-
function ConditionItem({ icon, label, value, colors }: { icon: keyof typeof Ionicons.glyphMap; label: string; value: string; colors: (typeof Colors)["light"] }) {
1180+
function ConditionItem({ icon, label, value, windDirection, colors }: { icon: keyof typeof Ionicons.glyphMap; label: string; value: string; windDirection?: number; colors: (typeof Colors)["light"] }) {
11631181
return (
11641182
<View style={styles.conditionItem}>
11651183
<Ionicons name={icon} size={20} color={colors.primary} />
11661184
<Text style={[styles.conditionLabel, { color: colors.textSecondary }]}>{label}</Text>
1167-
<Text style={[styles.conditionValue, { color: colors.text }]}>{value}</Text>
1185+
<View style={{ flexDirection: "row", alignItems: "center", gap: 4 }}>
1186+
<Text style={[styles.conditionValue, { color: colors.text }]}>{value}</Text>
1187+
{windDirection != null && (
1188+
<Text style={[styles.conditionValue, { color: colors.muted, transform: [{ rotate: `${getWindArrowRotation(windDirection)}deg` }] }]}></Text>
1189+
)}
1190+
</View>
11681191
</View>
11691192
);
11701193
}

mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "beta-rocks-mobile",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"private": true,
55
"main": "expo-router/entry",
66
"scripts": {

mobile/src/constants/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const SUPABASE_ANON_KEY =
1212
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || "sb_publishable_l3JH7lREcNNPqL6lHBxjuQ_KuX6jhEK";
1313

1414
export const APP_NAME = "beta.rocks";
15-
export const APP_VERSION = "0.4.0";
15+
export const APP_VERSION = "0.5.0";
1616

1717
/**
1818
* Rating colors matching web's getRatingColor()

mobile/src/lib/units.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,33 @@ export function convertPrecipitation(
112112
return value;
113113
}
114114

115+
/**
116+
* Convert wind direction in degrees to cardinal direction string
117+
*/
118+
const CARDINAL_DIRECTIONS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] as const;
119+
export type CardinalDirection = (typeof CARDINAL_DIRECTIONS)[number];
120+
121+
export function getWindCardinal(degrees: number): CardinalDirection {
122+
const normalized = ((degrees % 360) + 360) % 360;
123+
const index = Math.round(normalized / 45) % 8;
124+
return CARDINAL_DIRECTIONS[index];
125+
}
126+
127+
export function getWindArrowRotation(degrees: number): number {
128+
return (degrees + 180) % 360;
129+
}
130+
131+
export function formatWindWithDirection(
132+
speed: number,
133+
unit: UnitsConfig["windSpeed"],
134+
direction?: number,
135+
decimals = 0
136+
): string {
137+
const speedStr = formatWindSpeed(speed, unit, decimals);
138+
if (direction == null) return speedStr;
139+
return `${speedStr} ${getWindCardinal(direction)}`;
140+
}
141+
115142
export function formatPrecipitation(
116143
value: number,
117144
unit: UnitsConfig["precipitation"],

mobile/src/types/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface CurrentWeather {
2020
temperature_c: number;
2121
humidity: number;
2222
windSpeed_kph: number;
23+
windDirection?: number;
2324
precipitation_mm: number;
2425
weatherCode: number;
2526
}
@@ -34,6 +35,7 @@ export interface HourlyCondition {
3435
humidity: number;
3536
windSpeed_kph: number;
3637
wind_kph: number;
38+
wind_direction?: number;
3739
precipitation_mm: number;
3840
precip_mm: number;
3941
weatherCode: number;
@@ -61,6 +63,7 @@ export interface DailyForecast {
6163
tempMin: number;
6264
precipitation: number;
6365
windSpeedMax: number;
66+
windDirectionDominant?: number;
6467
sunrise: string;
6568
sunset: string;
6669
weatherCode: number;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "beta-rocks",
33
"private": true,
4-
"version": "0.4.0",
4+
"version": "0.5.0",
55
"workspaces": [
66
"mobile"
77
],

src/app/api/conditions/[cragId]/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export async function GET(
7171
temp_c: hour.temperature,
7272
humidity: hour.humidity,
7373
wind_kph: hour.windSpeed,
74+
wind_direction: hour.windDirection,
7475
precip_mm: hour.precipitation,
7576
weatherCode: hour.weatherCode,
7677
})),
@@ -99,6 +100,7 @@ export async function GET(
99100
temperature_c: weather.current.temperature,
100101
humidity: weather.current.humidity,
101102
windSpeed_kph: weather.current.windSpeed,
103+
windDirection: weather.current.windDirection,
102104
precipitation_mm: weather.current.precipitation,
103105
weatherCode: weather.current.weatherCode,
104106
},

src/app/api/conditions/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export async function GET(request: NextRequest) {
7070
temp_c: h.temperature,
7171
humidity: h.humidity,
7272
wind_kph: h.windSpeed,
73+
wind_direction: h.windDirection,
7374
precip_mm: h.precipitation,
7475
weatherCode: h.weatherCode,
7576
}));
@@ -105,6 +106,7 @@ export async function GET(request: NextRequest) {
105106
temperature_c: forecast.current.temperature,
106107
humidity: forecast.current.humidity,
107108
windSpeed_kph: forecast.current.windSpeed,
109+
windDirection: forecast.current.windDirection,
108110
precipitation_mm: forecast.current.precipitation,
109111
weatherCode: forecast.current.weatherCode,
110112
},
@@ -118,6 +120,7 @@ export async function GET(request: NextRequest) {
118120
tempMin: day.tempMin,
119121
precipitation: day.precipitation,
120122
windSpeedMax: day.windSpeedMax,
123+
windDirectionDominant: day.windDirectionDominant,
121124
sunrise: day.sunrise,
122125
sunset: day.sunset,
123126
weatherCode: day.weatherCode,

src/app/api/location/[slug]/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export async function GET(
6464
temp_c: hour.temperature,
6565
humidity: hour.humidity,
6666
wind_kph: hour.windSpeed,
67+
wind_direction: hour.windDirection,
6768
precip_mm: hour.precipitation,
6869
weatherCode: hour.weatherCode,
6970
})),
@@ -92,6 +93,7 @@ export async function GET(
9293
temperature_c: weather.current.temperature,
9394
humidity: weather.current.humidity,
9495
windSpeed_kph: weather.current.windSpeed,
96+
windDirection: weather.current.windDirection,
9597
precipitation_mm: weather.current.precipitation,
9698
weatherCode: weather.current.weatherCode,
9799
},

0 commit comments

Comments
 (0)