diff --git a/app/src/screens/LoginScreen.tsx b/app/src/screens/LoginScreen.tsx
index eef0e85..fa7a2c6 100644
--- a/app/src/screens/LoginScreen.tsx
+++ b/app/src/screens/LoginScreen.tsx
@@ -1,18 +1,20 @@
import React, { useState } from 'react';
import {
ActivityIndicator,
+ Image,
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
- View,
} from 'react-native';
import { useAuth } from '@/auth/AuthContext';
+import { useTheme } from '@/theme/ThemeContext';
export function LoginScreen() {
const { login, error } = useAuth();
+ const { colors } = useTheme();
const [phone, setPhone] = useState('');
const [password, setPassword] = useState('');
const [busy, setBusy] = useState(false);
@@ -30,23 +32,32 @@ export function LoginScreen() {
return (
- BigBrainParking
- Sign in with your phone number
+
+ BigBrainParking
+
+ Sign in with your phone number
+
{error} : null}
@@ -66,10 +77,9 @@ export function LoginScreen() {
)}
-
-
- Forgot your password? Use the official app or a reset SMS — this build reuses
- the same account.
+
+ Forgot your password? Reset it via the official app — this build reuses the same
+ account.
);
@@ -77,24 +87,19 @@ export function LoginScreen() {
const styles = StyleSheet.create({
container: { flex: 1, padding: 24, justifyContent: 'center' },
- title: { fontSize: 32, fontWeight: '700', textAlign: 'center' },
- subtitle: { fontSize: 15, color: '#666', textAlign: 'center', marginBottom: 24 },
+ logo: { width: 112, height: 112, alignSelf: 'center', marginBottom: 12, borderRadius: 24 },
+ title: { fontSize: 32, fontWeight: '800', textAlign: 'center' },
+ subtitle: { fontSize: 15, textAlign: 'center', marginBottom: 24 },
input: {
borderWidth: 1,
- borderColor: '#ccc',
borderRadius: 10,
padding: 14,
fontSize: 16,
marginBottom: 12,
},
- error: { color: '#c0392b', marginBottom: 12 },
- button: {
- backgroundColor: '#1e6f5c',
- borderRadius: 10,
- padding: 16,
- alignItems: 'center',
- },
+ error: { color: '#e0574a', marginBottom: 12, fontWeight: '600', textAlign: 'center' },
+ button: { borderRadius: 10, padding: 16, alignItems: 'center' },
buttonDisabled: { opacity: 0.6 },
- buttonText: { color: '#fff', fontWeight: '600', fontSize: 16 },
- hint: { color: '#888', fontSize: 12, textAlign: 'center' },
+ buttonText: { color: '#fff', fontWeight: '700', fontSize: 16 },
+ hint: { fontSize: 12, textAlign: 'center', marginTop: 16 },
});
diff --git a/app/src/screens/MapScreen.tsx b/app/src/screens/MapScreen.tsx
index 58242d7..2857600 100644
--- a/app/src/screens/MapScreen.tsx
+++ b/app/src/screens/MapScreen.tsx
@@ -178,6 +178,29 @@ export function MapScreen() {
}
};
+ // MapLibre's Camera re-applies its last imperative setCamera() stop whenever it
+ // re-renders — so a search re-render (setZones) would snap the map back to the
+ // last programmatic target. Memoize these elements so search re-renders never
+ // touch them; only the marker ShapeSource below updates.
+ const cameraEl = useMemo(() => , []);
+ const userLocationEl = useMemo(
+ () => (
+ {
+ if (loc?.coords) {
+ nativeFix.current = {
+ latitude: loc.coords.latitude,
+ longitude: loc.coords.longitude,
+ };
+ }
+ }}
+ />
+ ),
+ [],
+ );
+
return (
- {/* Uncontrolled camera; we position it imperatively (once on load, then
- only on explicit user actions) so re-renders never move the map. */}
-
-
- {/* renderMode="normal" draws the dot in the JS layer and does NOT engage
- the native location component, which was tracking the camera back to
- the user on re-render. */}
- {
- if (loc?.coords) {
- nativeFix.current = {
- latitude: loc.coords.latitude,
- longitude: loc.coords.longitude,
- };
- }
- }}
- />
+ {cameraEl}
+ {userLocationEl}
0) parts.push(`$${p.Rate}/hr`);
- if (p.MaxTime != null) parts.push(`${fmtMaxTime(p.MaxTime)} max`);
- return { title, sub: parts.join(' · ') };
+ const rate = typeof p.Rate === 'number' && p.Rate > 0 ? `$${p.Rate}/hr` : undefined;
+ return { title, rate, color };
}
export function MeterDetailScreen() {
@@ -86,13 +91,18 @@ export function MeterDetailScreen() {
{firstSpace?.Policies?.length ? (
Rate policies
- {firstSpace.Policies.slice(0, 8).map((p, i) => {
- const { title, sub } = policyLine(p);
+ {firstSpace.Policies.slice(0, 10).map((p, i) => {
+ const { title, rate, color } = policyMeta(p);
return (
- {title}
- {sub ? (
- {sub}
+
+
+ {title}
+
+ {rate ? (
+ {rate}
) : null}
);
@@ -134,9 +144,10 @@ const styles = StyleSheet.create({
fieldValue: { fontSize: 16, fontWeight: '600', marginTop: 2 },
card: { borderRadius: 10, padding: 14, marginTop: 16 },
cardTitle: { fontWeight: '700', marginBottom: 8, fontSize: 15 },
- policyRow: { marginBottom: 8 },
- policyTitle: { fontSize: 14, fontWeight: '600' },
- policySub: { fontSize: 12, marginTop: 1 },
+ policyRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 10 },
+ policyDot: { width: 9, height: 9, borderRadius: 5 },
+ policyTitle: { fontSize: 14, fontWeight: '600', flex: 1 },
+ policyRate: { fontSize: 13, fontWeight: '600' },
button: { marginTop: 16, borderRadius: 10, padding: 16, alignItems: 'center' },
buttonText: { fontWeight: '600', fontSize: 16 },
});