Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2de418c
feat: time and date-time questions, numeric display logic and unknown…
Oct 28, 2026
343bb3c
fix: keep older select display rules that name one option in value
Oct 28, 2026
3fe01b6
fix: show the splash screen logo at a readable size
Oct 28, 2026
715ed15
fix: back after deleting a submission no longer returns to its delete…
Oct 28, 2026
d3d59ba
Mobile: scroll the answers on the delete submission screen and pin th…
Nov 2, 2026
64febdb
Mobile: fix the Android crash after saving arrival and departure time
Nov 2, 2026
5b863e8
Mobile: stop reporting 404s, timeouts and network errors to Sentry
Nov 2, 2026
beb123c
Mobile: add time and date settings with 24-hour and YYYY-MM-DD defaults
Nov 3, 2026
57a9c74
Mobile: rename the time and date settings i18n namespace to dateTimeF…
Nov 3, 2026
8b93945
Mobile: stop the image picker decoding full-resolution photos
Nov 4, 2026
7a61356
Mobile: upload attachment parts from disk instead of base64 in JS
Nov 4, 2026
12467cc
Mobile: build the shared Button on XStack so its pressed look does no…
Nov 6, 2026
cf501e2
Mobile: show the English election title instead of the status in the …
Nov 6, 2026
4d4a472
Mobile: fix formatting
Nov 6, 2026
23d4f92
Mobile: move incident reporting forms to the report tab
Nov 6, 2026
d3ceceb
Mobile: keep cached data when a refetch fails while offline
Nov 6, 2026
00a8dee
Mobile: save incident reports while they are filled in and send them …
Nov 6, 2026
503928a
Mobile: move "Send report" on incident reports to the header
Nov 9, 2026
c8c1b8d
Mobile: incident report form menu, language switch and locked sent re…
Nov 9, 2026
1a1aa95
Mobile: address PR review comments, date answer values and tests
Nov 10, 2026
ba886d7
Mobile: send the incident report from the form menu
Nov 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
"orientation": "portrait",
"icon": "./assets/icons/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/icons/splash.png",
"resizeMode": "contain",
"backgroundColor": "#FDD20C"
},
"assetBundlePatterns": [
"**/*"
],
Expand Down Expand Up @@ -63,6 +58,18 @@
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/icons/splash-logo.png",
"resizeMode": "contain",
"backgroundColor": "#FDD20C",
"imageWidth": 190,
"ios": {
"imageWidth": 280
}
}
],
[
"expo-font",
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { router, useLocalSearchParams } from "expo-router";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { RefreshControl } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ScrollView, Separator, Spinner, YStack } from "tamagui";
import { getAnswerDisplay } from "../../../../../../../common/utils/answers";
import Button from "../../../../../../../components/Button";
Expand All @@ -13,6 +12,7 @@ import { Screen } from "../../../../../../../components/Screen";
import { Typography } from "../../../../../../../components/Typography";
import { useNetInfoContext } from "../../../../../../../contexts/net-info-banner/NetInfoContext";
import { useUserData } from "../../../../../../../contexts/user/UserContext.provider";
import { useDateTimeFormat } from "../../../../../../../contexts/date-time-format/DateTimeFormatContext.provider";
import { shouldDisplayQuestion } from "../../../../../../../services/form.parser";
import {
ApiFormAnswer,
Expand All @@ -35,6 +35,7 @@ type SearchParamsType = {

const DeleteFormSubmission = () => {
const { t } = useTranslation(["delete_submission", "common"]);
const dateTimeFormat = useDateTimeFormat();
const { formId, submissionId, language, submissionNumber } =
useLocalSearchParams<SearchParamsType>();
const { isOnline } = useNetInfoContext();
Expand All @@ -46,7 +47,6 @@ const DeleteFormSubmission = () => {
const { activeElectionRound, selectedPollingStation } = useUserData();
const [isRefreshing, setIsRefreshing] = useState(false);
const [showWarningModal, setShowWarningModal] = useState(false);
const insets = useSafeAreaInsets();

const { mutate: deleteSubmission } = useDeleteFormSubmissionMutation({
electionRoundId: activeElectionRound?.id,
Expand Down Expand Up @@ -254,7 +254,13 @@ const DeleteFormSubmission = () => {
{
onSuccess: () => {
setShowWarningModal(false);
router.push(`/multi-submission-form-details/${formId}?language=${language}`);
// leave this preview: back to the form's submissions it was opened from, so back from there doesn't
// return to a deleted submission
if (router.canGoBack()) {
router.back();
} else {
router.replace(`/multi-submission-form-details/${formId}?language=${language}`);
}
},
},
);
Expand Down Expand Up @@ -309,48 +315,64 @@ const DeleteFormSubmission = () => {
leftIcon={<Icon icon="chevronLeft" color="white" />}
onLeftPress={() => router.back()}
/>
<YStack paddingTop={28} gap="$xl" paddingHorizontal="$md" flex={1}>
<YStack flex={1} marginBottom={insets.bottom + 16}>
<Typography preset="heading"> {formTitle}</Typography>
{displayedQuestions.map((question) => (
<YStack key={question.id} marginTop="$md">
<Typography fontWeight="500">
{question.code} - {question.text[language]}
{/* The answers scroll; the delete button stays pinned under them so it is always reachable. */}
<ScrollView
flex={1}
contentContainerStyle={{ paddingTop: 28, paddingHorizontal: 16, paddingBottom: 16 }}
showsVerticalScrollIndicator
bounces={isOnline}
refreshControl={<RefreshControl refreshing={isRefreshing} onRefresh={handleRefetch} />}
>
<Typography preset="heading"> {formTitle}</Typography>
{displayedQuestions.map((question) => (
<YStack key={question.id} marginTop="$md">
<Typography fontWeight="500">
{question.code} - {question.text[language]}
</Typography>
{mappedAnswers && mappedAnswers[question.id] && (
<Typography marginTop="$xs" color="$gray5">
{getAnswerDisplay(
mappedAnswers[question.id] as ApiFormAnswer,
true,
dateTimeFormat,
)}
</Typography>
{mappedAnswers && mappedAnswers[question.id] && (
<Typography marginTop="$xs" color="$gray5">
{getAnswerDisplay(mappedAnswers[question.id] as ApiFormAnswer, true)}
)}
{attachments && attachments[question.id] ? (
<YStack gap="$xxs">
<Typography fontWeight="500">
{t("attachments", { value: attachments[question.id].length })}
</Typography>
</YStack>
) : (
false
)}

{notes && notes[question.id] ? (
<YStack gap="$xxs">
<Typography fontWeight="500">
{t("notes", { value: notes[question.id].length })}
</Typography>
)}
{attachments && attachments[question.id] ? (
<YStack gap="$xxs">
<Typography fontWeight="500">
{t("attachments", { value: attachments[question.id].length })}
</Typography>
</YStack>
) : (
false
)}

{notes && notes[question.id] ? (
<YStack gap="$xxs">
<Typography fontWeight="500">
{t("notes", { value: notes[question.id].length })}
</Typography>
</YStack>
) : (
false
)}
<Separator marginTop="$xs" />
</YStack>
))}

<YStack paddingHorizontal="$lg" paddingTop="$md">
<Button preset="red" onPress={() => setShowWarningModal(true)}>
{t("delete")}
</Button>
</YStack>
) : (
false
)}
<Separator marginTop="$xs" />
</YStack>
</YStack>
))}
</ScrollView>

<YStack
paddingHorizontal="$xl"
paddingTop="$md"
paddingBottom="$md"
borderTopWidth={1}
borderTopColor="$gray3"
backgroundColor="white"
>
<Button preset="red" onPress={() => setShowWarningModal(true)}>
{t("delete")}
</Button>
</YStack>

{showWarningModal && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import SingleSubmissionFormList from "../../../../../../components/SingleSubmiss
import ObservationSkeleton from "../../../../../../components/SkeletonLoaders/ObservationSkeleton";
import { Typography } from "../../../../../../components/Typography";
import { useUserData } from "../../../../../../contexts/user/UserContext.provider";
import { useSettledValue } from "../../../../../../hooks/useSettledValue";
import {
usePollingStationInformation,
usePollingStationInformationForm,
Expand All @@ -36,6 +37,9 @@ const Index = () => {
activeElectionRound?.id,
selectedPollingStation?.pollingStationId,
);
// saving arrival / departure time or the PSI form updates this data while the screen comes back into view;
// showing the change only once it's settled avoids an Android crash (see useSettledValue)
const shownPsiData = useSettledValue(psiData);

const { data: psiFormQuestions, isLoading: isLoadingPsiFormQuestions } =
usePollingStationInformationForm(activeElectionRound?.id);
Expand Down Expand Up @@ -192,7 +196,10 @@ const Index = () => {
<SingleSubmissionFormList
ListHeaderComponent={
<YStack>
<PollingStationGeneral psiData={psiData} psiFormQuestions={psiFormQuestions} />
<PollingStationGeneral
psiData={shownPsiData}
psiFormQuestions={psiFormQuestions}
/>
<Typography preset="body1" fontWeight="700" marginTop="$lg" marginBottom="$xxs">
{t("forms.heading")}
</Typography>
Expand All @@ -207,7 +214,10 @@ const Index = () => {
<MultiSubmissionFormList
ListHeaderComponent={
<YStack>
<PollingStationGeneral psiData={psiData} psiFormQuestions={psiFormQuestions} />
<PollingStationGeneral
psiData={shownPsiData}
psiFormQuestions={psiFormQuestions}
/>
<Typography preset="body1" fontWeight="700" marginTop="$lg" marginBottom="$xxs">
{t("forms.heading")}
</Typography>
Expand Down
Loading
Loading