Merge pull request #7140 from BlueWallet/notio

FIX: When user uses custom electrum server, make him aware that his a…
This commit is contained in:
GLaDOS 2024-10-11 11:32:37 +00:00 committed by GitHub
commit dacd26531c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 235 additions and 198 deletions

View file

@ -147,7 +147,7 @@ function Notifications(props) {
ActionSheet.showActionSheetWithOptions( ActionSheet.showActionSheetWithOptions(
{ {
title: loc.settings.notifications, title: loc.settings.notifications,
message: loc.notifications.would_you_like_to_receive_notifications, message: `${loc.notifications.would_you_like_to_receive_notifications}\n${loc.settings.push_notifications_explanation}`,
options, options,
cancelButtonIndex: 0, // Assuming 'no and don't ask' is still treated as the cancel action cancelButtonIndex: 0, // Assuming 'no and don't ask' is still treated as the cancel action
anchor: anchor ? findNodeHandle(anchor.current) : undefined, anchor: anchor ? findNodeHandle(anchor.current) : undefined,

View file

@ -302,8 +302,8 @@
"privacy_clipboard_explanation": "Provide shortcuts if an address or invoice is found in your clipboard.", "privacy_clipboard_explanation": "Provide shortcuts if an address or invoice is found in your clipboard.",
"privacy_do_not_track": "Disable Analytics", "privacy_do_not_track": "Disable Analytics",
"privacy_do_not_track_explanation": "Performance and reliability information will not be submitted for analysis.", "privacy_do_not_track_explanation": "Performance and reliability information will not be submitted for analysis.",
"push_notifications": "Push Notifications",
"rate": "Rate", "rate": "Rate",
"push_notifications_explanation": "By enabling notifications, your device token will be sent to the server, along with wallet addresses and transaction IDs for all wallets and transactions made after enabling notifications. The device token is used to send notifications, and the wallet information allows us to notify you about incoming Bitcoin or transaction confirmations.\n\nOnly information from after you enable notifications is transmitted—nothing from before is collected.\n\nDisabling notifications will remove all of this information from the server. Additionally, deleting a wallet from the app will also remove its associated information from the server.",
"selfTest": "Self-Test", "selfTest": "Self-Test",
"save": "Save", "save": "Save",
"saved": "Saved", "saved": "Saved",
@ -315,6 +315,7 @@
}, },
"notifications": { "notifications": {
"would_you_like_to_receive_notifications": "Would you like to receive notifications when you get incoming payments?", "would_you_like_to_receive_notifications": "Would you like to receive notifications when you get incoming payments?",
"notifications_subtitle": "Incoming payments and transaction confirmations",
"no_and_dont_ask": "No, and do not ask me again.", "no_and_dont_ask": "No, and do not ask me again.",
"ask_me_later": "Ask me later." "ask_me_later": "Ask me later."
}, },

View file

@ -14,7 +14,7 @@ const DefaultView = lazy(() => import('../screen/settings/DefaultView'));
const ElectrumSettings = lazy(() => import('../screen/settings/electrumSettings')); const ElectrumSettings = lazy(() => import('../screen/settings/electrumSettings'));
const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage')); const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage'));
const LightningSettings = lazy(() => import('../screen/settings/LightningSettings')); const LightningSettings = lazy(() => import('../screen/settings/LightningSettings'));
const NotificationSettings = lazy(() => import('../screen/settings/notificationSettings')); const NotificationSettings = lazy(() => import('../screen/settings/NotificationSettings'));
const SelfTest = lazy(() => import('../screen/settings/SelfTest')); const SelfTest = lazy(() => import('../screen/settings/SelfTest'));
const ReleaseNotes = lazy(() => import('../screen/settings/ReleaseNotes')); const ReleaseNotes = lazy(() => import('../screen/settings/ReleaseNotes'));
const Tools = lazy(() => import('../screen/settings/tools')); const Tools = lazy(() => import('../screen/settings/tools'));

View file

@ -0,0 +1,231 @@
import React, { useCallback, useEffect, useState } from 'react';
import { I18nManager, Linking, ScrollView, StyleSheet, TextInput, View, Pressable } from 'react-native';
import { Button as ButtonRNElements } from '@rneui/themed';
// @ts-ignore: no declaration file
import Notifications from '../../blue_modules/notifications';
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import { Button } from '../../components/Button';
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
import ListItem, { PressableWrapper } from '../../components/ListItem';
import { useTheme } from '../../components/themes';
import loc from '../../loc';
import { Divider } from '@rneui/base';
const NotificationSettings: React.FC = () => {
const [isLoading, setIsLoading] = useState(true);
const [isNotificationsEnabled, setNotificationsEnabled] = useState(false);
const [tokenInfo, setTokenInfo] = useState('<empty>');
const [URI, setURI] = useState<string | undefined>();
const [tapCount, setTapCount] = useState(0);
const { colors } = useTheme();
const stylesWithThemeHook = {
root: {
backgroundColor: colors.background,
},
scroll: {
backgroundColor: colors.background,
},
scrollBody: {
backgroundColor: colors.background,
},
uri: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
};
const handleTap = () => {
setTapCount(prevCount => prevCount + 1);
};
const onNotificationsSwitch = async (value: boolean) => {
try {
setNotificationsEnabled(value);
if (value) {
// User is enabling notifications
// @ts-ignore: refactor later
await Notifications.cleanUserOptOutFlag();
// @ts-ignore: refactor later
if (await Notifications.getPushToken()) {
// we already have a token, so we just need to reenable ALL level on groundcontrol:
// @ts-ignore: refactor later
await Notifications.setLevels(true);
} else {
// ok, we dont have a token. we need to try to obtain permissions, configure callbacks and save token locally:
// @ts-ignore: refactor later
await Notifications.tryToObtainPermissions();
}
} else {
// User is disabling notifications
// @ts-ignore: refactor later
await Notifications.setLevels(false);
}
// @ts-ignore: refactor later
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
} catch (error) {
console.error(error);
presentAlert({ message: (error as Error).message });
}
};
useEffect(() => {
(async () => {
try {
// @ts-ignore: refactor later
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
// @ts-ignore: refactor later
setURI(await Notifications.getSavedUri());
// @ts-ignore: refactor later
setTokenInfo(
'token: ' +
// @ts-ignore: refactor later
JSON.stringify(await Notifications.getPushToken()) +
' permissions: ' +
// @ts-ignore: refactor later
JSON.stringify(await Notifications.checkPermissions()) +
' stored notifications: ' +
// @ts-ignore: refactor later
JSON.stringify(await Notifications.getStoredNotifications()),
);
} catch (e) {
console.error(e);
presentAlert({ message: (e as Error).message });
} finally {
setIsLoading(false);
}
})();
}, []);
const save = useCallback(async () => {
setIsLoading(true);
try {
if (URI) {
// validating only if its not empty. empty means use default
// @ts-ignore: refactor later
if (await Notifications.isGroundControlUriValid(URI)) {
// @ts-ignore: refactor later
await Notifications.saveUri(URI);
presentAlert({ message: loc.settings.saved });
} else {
presentAlert({ message: loc.settings.not_a_valid_uri });
}
} else {
// @ts-ignore: refactor later
await Notifications.saveUri('');
presentAlert({ message: loc.settings.saved });
}
} catch (error) {
console.warn(error);
}
setIsLoading(false);
}, [URI]);
return (
<ScrollView style={stylesWithThemeHook.scroll} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
<ListItem
Component={PressableWrapper}
title={loc.settings.notifications}
subtitle={loc.notifications.notifications_subtitle}
disabled={isLoading}
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
/>
<Pressable onPress={handleTap}>
<BlueCard>
<BlueText style={styles.multilineText}>{loc.settings.push_notifications_explanation}</BlueText>
</BlueCard>
</Pressable>
{tapCount >= 10 && (
<>
<Divider />
<BlueCard>
<BlueText>{loc.settings.groundcontrol_explanation}</BlueText>
</BlueCard>
<ButtonRNElements
icon={{
name: 'github',
type: 'font-awesome',
color: colors.foregroundColor,
}}
onPress={() => Linking.openURL('https://github.com/BlueWallet/GroundControl')}
titleStyle={{ color: colors.buttonAlternativeTextColor }}
title="github.com/BlueWallet/GroundControl"
color={colors.buttonTextColor}
buttonStyle={styles.buttonStyle}
/>
<BlueCard>
<View style={[styles.uri, stylesWithThemeHook.uri]}>
<TextInput
// @ts-ignore: refactor later
placeholder={Notifications.getDefaultUri()}
value={URI}
onChangeText={setURI}
numberOfLines={1}
style={styles.uriText}
placeholderTextColor="#81868e"
editable={!isLoading}
textContentType="URL"
autoCapitalize="none"
underlineColorAndroid="transparent"
/>
</View>
<BlueSpacing20 />
<BlueText style={styles.centered} onPress={() => setTapCount(tapCount + 1)}>
Ground Control to Major Tom
</BlueText>
<BlueText style={styles.centered} onPress={() => setTapCount(tapCount + 1)}>
Commencing countdown, engines on
</BlueText>
<View>
<CopyToClipboardButton stringToCopy={tokenInfo} displayText={tokenInfo} />
</View>
<BlueSpacing20 />
<Button onPress={save} title={loc.settings.save} />
</BlueCard>
</>
)}
</ScrollView>
);
};
const styles = StyleSheet.create({
uri: {
flexDirection: 'row',
borderWidth: 1,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
alignItems: 'center',
borderRadius: 4,
},
centered: {
textAlign: 'center',
},
uriText: {
flex: 1,
color: '#81868e',
marginHorizontal: 8,
minHeight: 36,
height: 36,
},
buttonStyle: {
backgroundColor: 'transparent',
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
},
multilineText: {
textAlign: 'left',
lineHeight: 20,
paddingBottom: 10,
},
});
export default NotificationSettings;

View file

@ -1,195 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import { I18nManager, Linking, ScrollView, StyleSheet, TextInput, TouchableWithoutFeedback, View } from 'react-native';
import { Button as ButtonRNElements } from '@rneui/themed';
import Notifications from '../../blue_modules/notifications';
import { BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import { Button } from '../../components/Button';
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
import ListItem from '../../components/ListItem';
import { BlueCurrentTheme, useTheme } from '../../components/themes';
import loc from '../../loc';
const NotificationSettings = () => {
const [isLoading, setIsLoading] = useState(true);
const [isNotificationsEnabled, setNotificationsEnabled] = useState(false);
const [isShowTokenInfo, setShowTokenInfo] = useState(0);
const [tokenInfo, setTokenInfo] = useState('<empty>');
const [URI, setURI] = useState();
const { colors } = useTheme();
const onNotificationsSwitch = async value => {
setNotificationsEnabled(value); // so the slider is not 'jumpy'
if (value) {
// user is ENABLING notifications
await Notifications.cleanUserOptOutFlag();
if (await Notifications.getPushToken()) {
// we already have a token, so we just need to reenable ALL level on groundcontrol:
await Notifications.setLevels(true);
} else {
// ok, we dont have a token. we need to try to obtain permissions, configure callbacks and save token locally:
await Notifications.tryToObtainPermissions();
}
} else {
// user is DISABLING notifications
await Notifications.setLevels(false);
}
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
};
useEffect(() => {
(async () => {
try {
setNotificationsEnabled(await Notifications.isNotificationsEnabled());
setURI(await Notifications.getSavedUri());
setTokenInfo(
'token: ' +
JSON.stringify(await Notifications.getPushToken()) +
' permissions: ' +
JSON.stringify(await Notifications.checkPermissions()) +
' stored notifications: ' +
JSON.stringify(await Notifications.getStoredNotifications()),
);
} catch (e) {
console.debug(e);
presentAlert({ message: e.message });
} finally {
setIsLoading(false);
}
})();
}, []);
const stylesWithThemeHook = {
root: {
...styles.root,
backgroundColor: colors.background,
},
scroll: {
...styles.scroll,
backgroundColor: colors.background,
},
scrollBody: {
...styles.scrollBody,
backgroundColor: colors.background,
},
};
const save = useCallback(async () => {
setIsLoading(true);
try {
if (URI) {
// validating only if its not empty. empty means use default
if (await Notifications.isGroundControlUriValid(URI)) {
await Notifications.saveUri(URI);
presentAlert({ message: loc.settings.saved });
} else {
presentAlert({ message: loc.settings.not_a_valid_uri });
}
} else {
await Notifications.saveUri('');
presentAlert({ message: loc.settings.saved });
}
} catch (error) {
console.warn(error);
}
setIsLoading(false);
}, [URI]);
return isLoading ? (
<BlueLoading />
) : (
<ScrollView style={stylesWithThemeHook.scroll} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
<ListItem
Component={TouchableWithoutFeedback}
title={loc.settings.push_notifications}
subtitle={loc.settings.groundcontrol_explanation}
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }}
/>
<BlueSpacing20 />
<ButtonRNElements
icon={{
name: 'github',
type: 'font-awesome',
color: colors.foregroundColor,
}}
onPress={() => Linking.openURL('https://github.com/BlueWallet/GroundControl')}
titleStyle={{ color: colors.buttonAlternativeTextColor }}
title="github.com/BlueWallet/GroundControl"
color={colors.buttonTextColor}
buttonStyle={styles.buttonStyle}
/>
<BlueCard>
<View style={styles.uri}>
<TextInput
placeholder={Notifications.getDefaultUri()}
value={URI}
onChangeText={setURI}
numberOfLines={1}
style={styles.uriText}
placeholderTextColor="#81868e"
editable={!isLoading}
textContentType="URL"
autoCapitalize="none"
underlineColorAndroid="transparent"
/>
</View>
<BlueSpacing20 />
<BlueText style={styles.centered} onPress={() => setShowTokenInfo(isShowTokenInfo + 1)}>
Ground Control to Major Tom
</BlueText>
<BlueText style={styles.centered} onPress={() => setShowTokenInfo(isShowTokenInfo + 1)}>
Commencing countdown, engines on
</BlueText>
{isShowTokenInfo >= 9 && (
<View>
<CopyToClipboardButton stringToCopy={tokenInfo} displayText={tokenInfo} />
</View>
)}
<BlueSpacing20 />
<Button onPress={save} title={loc.settings.save} />
</BlueCard>
</ScrollView>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
uri: {
flexDirection: 'row',
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
minHeight: 44,
height: 44,
alignItems: 'center',
borderRadius: 4,
},
centered: {
textAlign: 'center',
},
uriText: {
flex: 1,
color: '#81868e',
marginHorizontal: 8,
minHeight: 36,
height: 36,
},
buttonStyle: {
backgroundColor: 'transparent',
flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row',
},
});
export default NotificationSettings;