Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 5 additions & 4 deletions mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"expo": {
"name": "VoteMonitor",
"slug": "vote-monitor",
"version": "2.3.2",
"version": "2.5.1",
"scheme": "mobile",
"orientation": "portrait",
"icon": "./assets/icons/icon.png",
Expand All @@ -18,7 +18,7 @@
"ios": {
"supportsTablet": true,
"bundleIdentifier": "org.commitglobal.votemonitor",
"buildNumber": "111",
"buildNumber": "113",
"config": {
"usesNonExemptEncryption": false
},
Expand All @@ -31,7 +31,7 @@
},
"android": {
"softwareKeyboardLayoutMode": "pan",
"versionCode": 115,
"versionCode": 123,
"adaptiveIcon": {
"foregroundImage": "./assets/icons/adaptive-icon.png",
"backgroundColor": "#FDD20C"
Expand Down Expand Up @@ -126,7 +126,8 @@
"project": "votemonitor-mobile"
}
],
"react-native-compressor"
"react-native-compressor",
"react-native-map-link"
]
}
}
125 changes: 113 additions & 12 deletions mobile/app/(observer)/(app)/(drawer)/(tabs)/(observation)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { DrawerActions } from "@react-navigation/native";
import * as Clipboard from "expo-clipboard";
import { router, useNavigation } from "expo-router";
import React, { useState } from "react";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Popup } from "react-native-map-link";
import Toast from "react-native-toast-message";
import { YStack } from "tamagui";
import SingleSubmissionFormList from "../../../../../../components/SingleSubmissionFormList";
import MultiSubmissionFormList from "../../../../../../components/MultiSubmissionFormList";
import Header from "../../../../../../components/Header";
import { Icon } from "../../../../../../components/Icon";
import MultiSubmissionFormList from "../../../../../../components/MultiSubmissionFormList";
import NoElectionRounds from "../../../../../../components/NoElectionRounds";
import NoVisitsExist from "../../../../../../components/NoVisitsExist";
import OptionsSheet from "../../../../../../components/OptionsSheet";
import { PollingStationGeneral } from "../../../../../../components/PollingStationGeneral";
import { Screen } from "../../../../../../components/Screen";
import SelectPollingStation from "../../../../../../components/SelectPollingStation";
import SingleSubmissionFormList from "../../../../../../components/SingleSubmissionFormList";
import ObservationSkeleton from "../../../../../../components/SkeletonLoaders/ObservationSkeleton";
import { Typography } from "../../../../../../components/Typography";
import { useUserData } from "../../../../../../contexts/user/UserContext.provider";
Expand All @@ -25,6 +28,7 @@ const Index = () => {
const { t } = useTranslation("observation");
const navigation = useNavigation();
const [openContextualMenu, setOpenContextualMenu] = useState(false);
const [openSelectNavigationAppSheet, setOpenSelectNavigationAppSheet] = useState(false);

const { isLoading, visits, selectedPollingStation, activeElectionRound } = useUserData();

Expand All @@ -36,6 +40,75 @@ const Index = () => {
const { data: psiFormQuestions, isLoading: isLoadingPsiFormQuestions } =
usePollingStationInformationForm(activeElectionRound?.id);

const options = useMemo(() => {
if (!selectedPollingStation) {
return;
}

// Find the matching visit to get address and level information
const matchingVisit = visits?.find(
(visit) => visit.pollingStationId === selectedPollingStation.pollingStationId,
);

const fullAddress = [
matchingVisit?.level1,
matchingVisit?.level2,
matchingVisit?.level3,
matchingVisit?.level4,
matchingVisit?.level5,
matchingVisit?.address,
]
.filter(Boolean)
.join(" ");

return {
address: fullAddress,
latitude: selectedPollingStation.latitude,
longitude: selectedPollingStation.longitude,
dialogTitle: t("navigate_to_polling_station.title"),
dialogMessage: t("navigate_to_polling_station.description"),
cancelText: t("navigate_to_polling_station.actions.cancel"),
};
}, [selectedPollingStation, visits]);

const handleCopyPollingStationInfo = async () => {
if (!selectedPollingStation) {
return;
}

// Find the matching visit to get address and level information
const matchingVisit = visits?.find(
(visit) => visit.pollingStationId === selectedPollingStation.pollingStationId,
);

const infoText = [
matchingVisit?.level1,
matchingVisit?.level2,
matchingVisit?.level3,
matchingVisit?.level4,
matchingVisit?.level5,
matchingVisit?.number,
matchingVisit?.address,
]
.filter(Boolean)
.join(" / ");

try {
await Clipboard.setStringAsync(infoText);
Toast.show({
type: "success",
text2: t("options_menu.copy_success_toast"),
});
setOpenContextualMenu(false);
} catch (error) {
console.error("Failed to copy polling station information:", error);
Toast.show({
type: "error",
text2: t("options_menu.copy_error_toast"),
});
}
};

if (!isLoading && !activeElectionRound) {
return <NoElectionRounds />;
}
Expand All @@ -61,21 +134,49 @@ const Index = () => {

{openContextualMenu && (
<OptionsSheet open setOpen={setOpenContextualMenu}>
<YStack
paddingVertical="$xxs"
paddingHorizontal="$sm"
onPress={() => {
setOpenContextualMenu(false);
router.push("/manage-polling-stations");
}}
>
<Typography preset="body1" color="$gray7" lineHeight={24}>
<YStack paddingHorizontal="$sm" gap="$xxs">
<Typography
preset="body1"
color="$gray7"
paddingVertical="$xs"
lineHeight={24}
onPress={() => {
setOpenContextualMenu(false);
router.push("/manage-polling-stations");
}}
>
{t("options_menu.manage_my_polling_stations")}
</Typography>
<Typography
preset="body1"
color="$gray7"
paddingVertical="$xs"
lineHeight={24}
onPress={handleCopyPollingStationInfo}
>
{t("options_menu.copy_polling_station_information")}
</Typography>
<Typography
preset="body1"
color="$gray7"
paddingVertical="$xs"
lineHeight={24}
onPress={() => setOpenSelectNavigationAppSheet(true)}
>
{t("options_menu.navigate_to_polling_station")}
</Typography>
</YStack>
</OptionsSheet>
)}

<Popup
isVisible={openSelectNavigationAppSheet}
setIsVisible={setOpenSelectNavigationAppSheet}
onCancelPressed={() => setOpenSelectNavigationAppSheet(false)}
onAppPressed={() => setOpenSelectNavigationAppSheet(false)}
options={options ?? {}}
/>

<YStack paddingHorizontal="$md" flex={1}>
{(isLoading || isLoadingPsiData || isLoadingPsiFormQuestions) && <ObservationSkeleton />}
{activeElectionRound &&
Expand Down
54 changes: 24 additions & 30 deletions mobile/app/(observer)/(app)/polling-station-questionnaire.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,10 @@ const PollingStationQuestionnaire = () => {
});

const currentLanguage = useMemo(() => {
const activeLanguage = language ? language.toLocaleUpperCase() : i18n.language.toLocaleUpperCase();

const activeLanguage = language
? language.toLocaleUpperCase()
: i18n.language.toLocaleUpperCase();

if (
formStructure &&
formStructure?.defaultLanguage &&
Expand Down Expand Up @@ -169,10 +171,10 @@ const PollingStationQuestionnaire = () => {
}));
return mappedSelections?.length
? ({
$answerType: "multiSelectAnswer",
questionId,
selection: mappedSelections,
} as ApiFormAnswer)
$answerType: "multiSelectAnswer",
questionId,
selection: mappedSelections,
} as ApiFormAnswer)
: undefined;
}
default:
Expand Down Expand Up @@ -296,9 +298,7 @@ const PollingStationQuestionnaire = () => {
const onConfirmFormLanguage = (formId: string, language: string) => {
setFormLanguagePreference({ formId, language });

router.replace(
`/polling-station-questionnaire?language=${language}`,
);
router.replace(`/polling-station-questionnaire?language=${language}`);
setIsChangeLanguageModalOpen(false);
};

Expand Down Expand Up @@ -607,15 +607,15 @@ const PollingStationQuestionnaire = () => {
/>
)}

{isChangeLanguageModalOpen && formStructure?.languages && (
<ChangeLanguageDialog
currentLanguage={currentLanguage}
formId={formStructure?.id as string}
languages={formStructure.languages}
onCancel={setIsChangeLanguageModalOpen.bind(null, false)}
onSelectLanguage={onConfirmFormLanguage}
/>
)}
{isChangeLanguageModalOpen && formStructure?.languages && (
<ChangeLanguageDialog
currentLanguage={currentLanguage}
formId={formStructure?.id as string}
languages={formStructure.languages}
onCancel={setIsChangeLanguageModalOpen.bind(null, false)}
onSelectLanguage={onConfirmFormLanguage}
/>
)}
</Screen>

<XStack
Expand Down Expand Up @@ -655,14 +655,11 @@ const OptionSheetContent = ({

return (
<View paddingVertical="$xxs" paddingHorizontal="$sm" gap="$xxs">
<Typography preset="body1" paddingVertical="$xs" onPress={onChangeLanguagePress}>
{t("menu.change_language")}
</Typography>
<Typography
preset="body1"
color={disableMarkAsDone ? "$gray3" : "$gray7"}
lineHeight={24}
color={disableMarkAsDone ? "$gray3" : "$gray9"}
paddingVertical="$xs"
lineHeight={24}
onPress={() => {
onSetCompletion(!isCompleted);
}}
Expand All @@ -672,13 +669,10 @@ const OptionSheetContent = ({
? t("forms.mark_as_done", { ns: "common" })
: t("forms.mark_as_in_progress", { ns: "common" })}
</Typography>
<Typography
preset="body1"
color="$gray7"
lineHeight={24}
paddingVertical="$xs"
onPress={onClear}
>
<Typography preset="body1" paddingVertical="$xs" onPress={onChangeLanguagePress}>
{t("menu.change_language")}
</Typography>
<Typography preset="body1" color="$red10" paddingVertical="$xs" onPress={onClear}>
{t("menu.clear")}
</Typography>
</View>
Expand Down
4 changes: 3 additions & 1 deletion mobile/app/(observer)/(app)/polling-station-wizzard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,15 @@ const PollingStationWizzardContent = ({
pollingStationId: pollingStation.pollingStationId,
visitedAt: new Date().toISOString(),
address: pollingStation.name,
number: pollingStation?.number || "",
number: pollingStation.number ?? "",
isNotSynced: true,
level1: steps[0].name,
level2: steps[1]?.name,
level3: steps[2]?.name,
level4: steps[3]?.name,
level5: steps[4]?.name,
latitude: pollingStation.latitude,
longitude: pollingStation.longitude,
},
],
);
Expand Down
13 changes: 11 additions & 2 deletions mobile/assets/locales/az/translations_AZ.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,16 @@
}
},
"options_menu": {
"manage_my_polling_stations": "Mənim seçki məntəqələrimin idarə edilməsi"
"manage_my_polling_stations": "Mənim seçki məntəqələrimin idarə edilməsi",
"copy_polling_station_information": "Seçki məntəqəsi məlumatlarını mübadilə buferinə kopyala",
"copy_success_toast": "Seçki məntəqəsi məlumatları mübadilə buferinə kopyalandı",
"copy_error_toast": "Seçki məntəqəsi məlumatlarını kopyalamaq mümkün olmadı",
"navigate_to_polling_station": "Seçki məntəqəsinə keçid et"
},
"navigate_to_polling_station": {
"title": "Seçki məntəqəsinə keçid et",
"description": "Seçki məntəqəsinə istiqamətləri almaq üçün tətbiq seçin.",
"cancel": "Ləğv etmək"
}
},
"polling_station_information_form": {
Expand Down Expand Up @@ -890,4 +899,4 @@
"attachments": "Əlavələr:",
"notes": "Qeydlər:"
}
}
}
13 changes: 11 additions & 2 deletions mobile/assets/locales/bg/translations_BG.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,11 @@
"form_details_button_label": "Информация за изборната секция",
"number_of_questions": "{{value}} въпроса",
"answer_questions": "Отговорете на въпросите"
},
"navigate_to_polling_station": {
"title": "Преминаване до избирателната секция",
"description": "Изберете приложение, за да получите упътвания до избирателната секция",
"cancel": "Откажете"
}
},
"forms": {
Expand All @@ -380,7 +385,11 @@
}
},
"options_menu": {
"manage_my_polling_stations": "Управление на моите изборни секции"
"manage_my_polling_stations": "Управление на моите изборни секции",
"copy_polling_station_information": "Копиране на данните за изборната секция в клипборда",
"copy_success_toast": "Данните за изборната секция бяха копирани в клипборда",
"copy_error_toast": "Неуспешно копиране на данните за изборната секция",
"navigate_to_polling_station": "Преминаване към избирателната секция"
}
},
"polling_station_information_form": {
Expand Down Expand Up @@ -890,4 +899,4 @@
"attachments": "Прикачени файлове:",
"notes": "Бележки:"
}
}
}
Loading
Loading