Skip to content

Commit c1135c3

Browse files
Merge pull request #429 from OpenDTU-App/241-ask-if-user-wants-to-discard-settings-before-allowing-refresh
2 parents 5e8315d + 00c73da commit c1135c3

10 files changed

Lines changed: 266 additions & 58 deletions

pr-all-unpushed-branches.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash
2+
# first, create a list of all local branches
3+
LOCAL_BRANCHES=$(git for-each-ref --format='%(refname:short)' refs/heads/)
4+
5+
# remove all branches that have a remote tracking branch
6+
LOCAL_BRANCHES=$(echo "$LOCAL_BRANCHES" | while read BRANCH; do
7+
if [ -z "$(git for-each-ref --format='%(upstream:short)' refs/heads/$BRANCH)" ]; then
8+
echo "$BRANCH"
9+
fi
10+
done)
11+
12+
# then, remove branches that have been pushed to the remote
13+
# shellcheck disable=SC2001
14+
LOCAL_BRANCHES_FORMATTED=$(echo "$LOCAL_BRANCHES" | sed 's|^|refs/heads/|')
15+
16+
REMOTE_BRANCHES=$(git ls-remote origin --heads "$LOCAL_BRANCHES_FORMATTED" | awk '{print $2}' | sed 's|refs/heads/||')
17+
for BRANCH in $REMOTE_BRANCHES; do
18+
LOCAL_BRANCHES=$(echo "$LOCAL_BRANCHES" | grep -v "^$BRANCH$")
19+
done
20+
21+
# now, LOCAL_BRANCHES contains only branches that have not been pushed to the remote
22+
# for each of these branches, create a pull request using the GitHub CLI
23+
for BRANCH in $LOCAL_BRANCHES; do
24+
# echo "Creating pull request for branch: $BRANCH"
25+
# push the branch to the remote
26+
if ! git push -u origin "$BRANCH"; then
27+
echo "Failed to push branch: $BRANCH"
28+
continue
29+
fi
30+
31+
PR_EXISTS=$(gh pr list --head "$BRANCH" --json number --jq '.[].number')
32+
if [ -z "$PR_EXISTS" ]; then
33+
echo "Creating pull request for branch: $BRANCH"
34+
if ! gh pr create -B develop -d -H "$BRANCH" -f -a "@me"; then
35+
echo "Failed to create pull request for branch: $BRANCH"
36+
continue
37+
else
38+
echo "Pull request created for branch: $BRANCH"
39+
fi
40+
else
41+
echo "Pull request already exists for branch: $BRANCH (PR #$PR_EXISTS)"
42+
fi
43+
done

src/components/modals/ChangeBooleanValueModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const ChangeBooleanValueModal: FC<ChangeBooleanValueModalProps> = ({
3535
const [wasModified, setWasModified] = useState<boolean>(false);
3636

3737
const handleSave = () => {
38+
setWasModified(false);
3839
onSave?.(value);
3940
onClose?.();
4041
};

src/components/modals/ChangeEnumValueModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const ChangeEnumValueModal: FC<ChangeEnumValueModalProps> = ({
4444
const [wasModified, setWasModified] = useState<boolean>(false);
4545

4646
const handleSave = () => {
47+
setWasModified(false);
4748
onSave?.(value);
4849
onClose?.();
4950
};
@@ -98,7 +99,6 @@ const ChangeEnumValueModal: FC<ChangeEnumValueModalProps> = ({
9899
<RadioButton.Group onValueChange={setValue} value={value}>
99100
{possibleValues.map(({ label, value }) => (
100101
<RadioButton.Item
101-
key={value}
102102
value={value}
103103
label={label}
104104
labelVariant="bodyMedium"

src/components/modals/ChangeTextValueModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const ChangeTextValueModal: FC<NewChangeTextValueModalProps> = ({
6565
return;
6666
}
6767

68+
setWasModified(false);
6869
onSave?.(value);
6970
onClose?.();
7071
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { FC } from 'react';
2+
import { useCallback } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import { Box } from 'react-native-flex-layout';
5+
import type { ModalProps } from 'react-native-paper';
6+
import { Button, Portal, Text, useTheme } from 'react-native-paper';
7+
8+
import BaseModal from '@/components/BaseModal';
9+
10+
export type ConfirmUnsavedDataModalInput = false | (() => void);
11+
12+
export interface ConfirmUnsavedDataModalProps
13+
extends Omit<ModalProps, 'children' | 'visible'> {
14+
visible: ConfirmUnsavedDataModalInput;
15+
}
16+
17+
const ConfirmUnsavedDataModal: FC<ConfirmUnsavedDataModalProps> = props => {
18+
const { onDismiss, visible } = props;
19+
const { t } = useTranslation();
20+
const theme = useTheme();
21+
22+
const handleAbort = useCallback(() => {
23+
onDismiss?.();
24+
}, [onDismiss]);
25+
26+
return (
27+
<Portal>
28+
<BaseModal {...props} visible={!!visible}>
29+
<Box p={16}>
30+
<Box mb={8}>
31+
<Text variant="bodyLarge">{t('unsavedDataTips')}</Text>
32+
</Box>
33+
</Box>
34+
<Box
35+
style={{
36+
flexDirection: 'row',
37+
justifyContent: 'flex-end',
38+
alignItems: 'center',
39+
padding: 8,
40+
}}
41+
>
42+
<Button mode="text" onPress={handleAbort} style={{ marginRight: 8 }}>
43+
{t('cancel')}
44+
</Button>
45+
<Button
46+
onPress={() => {
47+
if (typeof visible === 'function') {
48+
visible();
49+
}
50+
onDismiss?.();
51+
}}
52+
mode="contained"
53+
buttonColor={theme.colors.primary}
54+
>
55+
{t('confirm')}
56+
</Button>
57+
</Box>
58+
</BaseModal>
59+
</Portal>
60+
);
61+
};
62+
63+
export default ConfirmUnsavedDataModal;

src/components/styled/SettingsSurface.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { Surface, useTheme } from 'react-native-paper';
44

55
import styled from 'styled-components';
66

7-
const settingsSurfaceBorderRadiusFactor = 3;
7+
const settingsSurfaceBorderRadiusFactor = 8;
88

99
export const settingsSurfaceRoundness = (theme: ThemeBase) => {
1010
return theme.roundness! * settingsSurfaceBorderRadiusFactor;
1111
};
1212

1313
const InternalSettingsSurface = styled(Surface)`
1414
margin: 4px 16px 12px;
15+
padding: 0 4px;
1516
border-radius: ${props =>
1617
(props.theme.roundness ?? 0) * settingsSurfaceBorderRadiusFactor}px;
1718
`;

src/translations

src/views/navigation/screens/SettingsGroup/DtuSettingsScreen.tsx

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { NRFPaLevel } from '@/types/opendtu/settings';
1313

1414
import ChangeEnumValueModal from '@/components/modals/ChangeEnumValueModal';
1515
import ChangeTextValueModal from '@/components/modals/ChangeTextValueModal';
16+
import type { ConfirmUnsavedDataModalInput } from '@/components/modals/ConfirmUnsavedDataModal';
17+
import ConfirmUnsavedDataModal from '@/components/modals/ConfirmUnsavedDataModal';
1618
import SettingsSurface from '@/components/styled/SettingsSurface';
1719

1820
import useDtuSettings from '@/hooks/useDtuSettings';
@@ -66,11 +68,28 @@ const DtuSettingsScreen: FC<PropsWithNavigation> = ({ navigation }) => {
6668
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
6769
const [isSaving, setIsSaving] = useState<boolean>(false);
6870

69-
const handleGetDtuSettings = useCallback(async () => {
70-
setIsRefreshing(true);
71-
await openDtuApi.getDtuConfig();
72-
setIsRefreshing(false);
73-
}, [openDtuApi]);
71+
const hasChanges = useMemo(() => {
72+
return !deepEqual(initialDtuSettings, dtuSettings);
73+
}, [initialDtuSettings, dtuSettings]);
74+
75+
const [confirmRefreshDataModalOpen, setConfirmRefreshDataModalOpen] =
76+
useState<ConfirmUnsavedDataModalInput>(false);
77+
78+
const performRefresh = useCallback(
79+
async (forceRefresh: boolean = false) => {
80+
if (hasChanges && !forceRefresh) {
81+
setConfirmRefreshDataModalOpen(() => () => {
82+
performRefresh(true);
83+
});
84+
return;
85+
}
86+
87+
setIsRefreshing(true);
88+
await openDtuApi.getDtuConfig();
89+
setIsRefreshing(false);
90+
},
91+
[hasChanges, openDtuApi],
92+
);
7493

7594
const handleSave = useCallback(async () => {
7695
if (!dtuSettings) {
@@ -95,13 +114,11 @@ const DtuSettingsScreen: FC<PropsWithNavigation> = ({ navigation }) => {
95114

96115
useEffect(() => {
97116
if (navigation.isFocused()) {
98-
handleGetDtuSettings();
117+
performRefresh();
99118
}
100-
}, [handleGetDtuSettings, navigation]);
101-
102-
const hasChanges = useMemo(() => {
103-
return !deepEqual(initialDtuSettings, dtuSettings);
104-
}, [initialDtuSettings, dtuSettings]);
119+
// we do not want to include performRefresh here
120+
// eslint-disable-next-line react-hooks/exhaustive-deps
121+
}, [navigation]);
105122

106123
const [changeSerialModalOpen, setChangeSerialModalOpen] = useState(false);
107124
const [changePollIntervalModalOpen, setChangePollIntervalModalOpen] =
@@ -148,7 +165,17 @@ const DtuSettingsScreen: FC<PropsWithNavigation> = ({ navigation }) => {
148165
return (
149166
<>
150167
<Appbar.Header>
151-
<Appbar.BackAction onPress={() => navigation.goBack()} />
168+
<Appbar.BackAction
169+
onPress={() => {
170+
if (hasChanges) {
171+
setConfirmRefreshDataModalOpen(() => () => {
172+
navigation.goBack();
173+
});
174+
return;
175+
}
176+
navigation.goBack();
177+
}}
178+
/>
152179
<Appbar.Content title={t('settings.dtuSettings.title')} />
153180
{isSaving || hasChanges ? (
154181
<Appbar.Action
@@ -165,7 +192,7 @@ const DtuSettingsScreen: FC<PropsWithNavigation> = ({ navigation }) => {
165192
refreshControl={
166193
<RefreshControl
167194
refreshing={isRefreshing}
168-
onRefresh={handleGetDtuSettings}
195+
onRefresh={performRefresh}
169196
colors={[theme.colors.primary]}
170197
progressBackgroundColor={theme.colors.elevation.level3}
171198
tintColor={theme.colors.primary}
@@ -501,6 +528,12 @@ const DtuSettingsScreen: FC<PropsWithNavigation> = ({ navigation }) => {
501528
return true;
502529
}}
503530
/>
531+
<ConfirmUnsavedDataModal
532+
visible={confirmRefreshDataModalOpen}
533+
onDismiss={() => {
534+
setConfirmRefreshDataModalOpen(false);
535+
}}
536+
/>
504537
</>
505538
);
506539
};

0 commit comments

Comments
 (0)