diff --git a/RELEASE-NOTES.json b/RELEASE-NOTES.json index 555f5c35..b7e53f77 100644 --- a/RELEASE-NOTES.json +++ b/RELEASE-NOTES.json @@ -7,7 +7,8 @@ "Auto self-spotting every 10 minutes", "Allow spaces in commands (try 'SPOT HERE')", "Fix missing keystrokes on Android when pressing 'send' too fast", - "Note expansion now works with callsign stacking" + "Note expansion now works with callsign stacking", + "Added Big Thumbs mode to enable logging from the spot list" ] }, "25.9.3": { diff --git a/src/App.jsx b/src/App.jsx index eb7337ad..8a902f29 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -49,6 +49,7 @@ import SpotsScreen from './screens/SpotsScreen/SpotsScreen' import OpInfoScreen from './screens/OperationScreens/OpInfoScreen' import OperationDetailsScreen from './screens/OperationScreens/OpSettingsTab/OperationDetailsScreen' import OperationLocationScreen from './screens/OperationScreens/OpSettingsTab/OperationLocationScreen' +import OpSpotsModal from './screens/OperationScreens/OpSpotsTab/OpSpotsModal' const Stack = createNativeStackNavigator() @@ -192,6 +193,11 @@ function MainApp ({ navigationTheme }) { options={{ title: 'Settings', headerShown: false }} component={MainSettingsScreen} /> + + ) diff --git a/src/screens/OperationScreens/OpSpotsTab/OpSpotsModal.jsx b/src/screens/OperationScreens/OpSpotsTab/OpSpotsModal.jsx new file mode 100644 index 00000000..8e5b7ec4 --- /dev/null +++ b/src/screens/OperationScreens/OpSpotsTab/OpSpotsModal.jsx @@ -0,0 +1,346 @@ +/* + * Copyright ©️ 2025 Cainan Whelchel + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { useEffect, useState, useCallback } from 'react' +import { View, Text } from 'react-native' +import { useDispatch, useSelector } from 'react-redux' +import cloneDeep from 'clone-deep' + +import { parseCallsign } from '@ham2k/lib-callsigns' +import { bandForFrequency } from '@ham2k/lib-operation-data' + +import { annotateQSO, useCallLookup } from '../OpLoggingTab/components/LoggingPanel/useCallLookup.js' +import { H2kPressable, H2kMarkdown } from '../../../ui' +import { trackEvent } from '../../../distro' +import { findHooks } from '../../../extensions/registry' +import { addQSOs, selectQSOs } from '../../../store/qsos' +import { selectSettings } from '../../../store/settings' +import { selectOperationCallInfo } from '../../../store/operations' +import { selectVFO } from '../../../store/station/stationSlice' +import { logTimer } from '../../../tools/perfTools' +import { expandRSTValues, parseStackedCalls } from '../../../tools/callsignTools' +import { useThemedStyles } from '../../../styles/tools/useThemedStyles' + +const DEBUG = false +let submitTimeout + +function prepareStyles (themeStyles, themeColor) { + // console.log(themeStyles) + + const white = '#fff' + const black = '#000' + const grey = '#bbb' + // const grey2 = '#222' + const grey3 = '#333' + + const commonStyles = { + fontSize: themeStyles.normalFontSize, + lineHeight: themeStyles.normalFontSize * 1.3, + borderWidth: DEBUG ? 1 : 0, + color: (themeStyles.theme.dark) ? white : black + } + + const commonButton = { + alignItems: 'center', + flex: 0, + justifyContent: 'center', + borderRadius: 10, + borderWidth: 3, + borderColor: grey3 + } + + return { + ...themeStyles, + panel: { + backgroundColor: themeStyles.theme.colors[`${themeColor}Container`], + borderBottomColor: themeStyles.theme.colors[`${themeColor}Light`], + borderTopColor: themeStyles.theme.colors[`${themeColor}Light`], + borderBottomWidth: 1, + paddingTop: themeStyles.oneSpace, + paddingBottom: themeStyles.oneSpace, + flexDirection: 'column', + color: themeStyles.theme.colors[`on${themeColor}`] + }, + container: { + paddingHorizontal: themeStyles.oneSpace, + paddingTop: themeStyles.oneSpace, + paddingBottom: themeStyles.oneSpace, + gap: themeStyles.halfSpace + }, + title: { + ...themeStyles.title, + color: (themeStyles.theme.dark) ? '#fff' : '#000', + marginBottom: 40 + }, + buttons: { + log: { + ...commonButton, + width: 175, + height: 175, + backgroundColor: (themeStyles.theme.dark) ? '#149c21ff' : '#25582aff' + }, + cancel: { + ...commonButton, + width: 175, + height: 175, + backgroundColor: (themeStyles.theme.dark) ? '#b83202ff' : '#9f0101ff' + }, + text: { + ...commonStyles, + fontSize: themeStyles.normalFontSize * 1.3, + lineHeight: themeStyles.normalFontSize * 1.5, + textAlign: 'center', + marginTop: 10, + marginBottom: 10, + color: white + } + }, + fields: { + callAndEmoji: { + ...commonStyles, + flexDirection: 'row', + alignItems: 'center', + marginLeft: themeStyles.oneSpace * 1.45, + minWidth: themeStyles.oneSpace * 5 + }, + call: { + ...commonStyles, + ...themeStyles.text.callsign, + fontWeight: 800, + fontSize: themeStyles.normalFontSize * 3.0, + lineHeight: themeStyles.normalFontSize * 2.5 + }, + band: { + ...commonStyles, + flex: 0, + marginBottom: themeStyles.oneSpace * 1 + }, + opName: { + ...commonStyles, + flex: 0, + marginBottom: themeStyles.oneSpace * 2.5, + fontWeight: 700, + fontSize: themeStyles.normalFontSize * 1.8, + lineHeight: themeStyles.normalFontSize * 2.5 + }, + note: { + ...commonStyles, + flex: 0, + marginBottom: themeStyles.oneSpace * 1, + fontWeight: 500, + fontSize: themeStyles.normalFontSize * 1.2, + color: (themeStyles.theme.dark) ? grey : grey3 + }, + mode: { + ...commonStyles, + flex: 0, + marginLeft: themeStyles.oneSpace * 0.2, + width: themeStyles.oneSpace * 5, + textAlign: 'right', + marginRight: themeStyles.oneSpace * 1.4, + color: (themeStyles.theme.dark) ? grey : grey3 + }, + icon: { + ...commonStyles, + flex: 0, + textAlign: 'left', + marginRight: themeStyles.oneSpace * 0.3, + marginLeft: themeStyles.oneSpace * -0.5, + marginTop: themeStyles.oneSpace * 0.2 + }, + label: { + ...commonStyles, + fontSize: themeStyles.normalFontSize * 1.2, + lineHeight: themeStyles.normalFontSize * 1.5, + textAlign: 'center', + marginTop: 10, + marginBottom: 10, + color: (themeStyles.theme.dark) ? grey : grey3 + } + } + } +} + +export default function OpSpotsModal ({ navigation, route }) { + const [isValidQSO, setIsValidQSO] = useState(false) + const themeColor = 'primary' + const styles = useThemedStyles(prepareStyles, themeColor) + const dispatch = useDispatch() + + const operation = route.params.operation + const qsos = useSelector(state => selectQSOs(state, route.params.operation.uuid)) + const settings = useSelector(selectSettings) + const ourInfo = useSelector(state => selectOperationCallInfo(state, operation?.uuid)) + const vfo = useSelector(state => selectVFO(state)) + + const { guess } = useCallLookup(route.params.qso) + + useEffect(() => { // Validate and analyze the callsign + const { call } = parseStackedCalls(route.params.qso?.their?.call ?? '') + + const callInfo = parseCallsign(call) + + if (callInfo?.baseCall || call.indexOf('?') >= 0) { + setIsValidQSO(true) + } else { + setIsValidQSO(false) + } + }, [route.params.qso?.their?.call]) + + // Since our fields and logic often perform some async work, + // we need to wait a few milliseconds before submitting to ensure all async work is complete. + // But we can't just use a timeout, because we need the function to bind to the latest values. + // So we use a state variable and a callback function to set it and an effect to actually submit.. + const [doSubmit, setDoSubmit] = useState(false) + + const handleSubmit = useCallback(() => { // + if (submitTimeout) clearTimeout(submitTimeout) + + submitTimeout = setTimeout(() => { + setDoSubmit(true) + }, 50) + }, [setDoSubmit]) + + useEffect(() => { + if (!doSubmit) return + + setDoSubmit(false) + + // copy out the params' qso to operate on it + const qso = cloneDeep(route.params.qso) + + setTimeout(async () => { // Run inside a setTimeout to allow for async functions + if (isValidQSO && !qso.deleted) { + delete qso._isNew + delete qso._willBeDeleted + delete qso.deleted + + if (qso.freq) { + qso.band = bandForFrequency(qso.freq) + } + + if (!qso.startAtMillis) qso.startAtMillis = (new Date()).getTime() + qso.startAt = new Date(qso.startAtMillis).toISOString() + if (qso.endAtMillis) qso.endAt = new Date(qso.endAtMillis).toISOString() + qso.our = qso.our || {} + qso.our.call = qso.our.call || ourInfo?.call + qso.our.operatorCall = qso.our.operatorCall || operation.local?.operatorCall + qso.our.sent = expandRSTValues(qso.our.sent, qso.mode) + + qso.their = qso.their || {} + qso.their.sent = expandRSTValues(qso.their.sent, qso.mode) + // let lastUUID + + const { call, allCalls } = parseStackedCalls(qso?.their?.call ?? '') + + const multiQSOs = [] + + for (let i = 0; i < allCalls.length; i++) { + let oneQSO = qso + qso.their.call = call + if (allCalls.length > 1) { // If this is a multi-call QSO, we need to clone and annotate the QSO for each call + console.log('preclone ') + console.log(qso) + oneQSO = cloneDeep(qso) + console.log('postclone ') + console.log(oneQSO) + if (i > 0) oneQSO.uuid = null + oneQSO.their.call = allCalls[i]?.trim() + oneQSO.their.guess = {} + oneQSO.their.lookup = {} + oneQSO = await annotateQSO({ qso: oneQSO, online: false, settings, dispatch }) + console.log('this here is the problem') + console.log(oneQSO) + oneQSO._needsLookup = true + } + multiQSOs.push(oneQSO) + + const eventName = 'add_qso' + + trackEvent(eventName, { their_prefix: oneQSO.their?.entityPrefix ?? oneQSO.their?.guess?.entityPrefix, refs: (oneQSO.refs || []).map(r => r.type).join(',') }) + + // lastUUID = oneQSO.uuid + } + + const activities = findHooks('activity').filter(activity => activity.processQSOBeforeSaveWithDispatch || activity.processQSOBeforeSave) + for (const activity of activities) { + for (const q of multiQSOs) { + if (activity.processQSOBeforeSaveWithDispatch) { + activity.processQSOBeforeSaveWithDispatch({ qso: q, operation, qsos, vfo, settings, dispatch }) + } else { + activity.processQSOBeforeSave({ qso: q, operation, qsos, vfo, settings }) + } + } + } + + setTimeout(() => { + // Add the QSO to the operation, and set a new QSO + // But leave enough time for blur effects to take place before being overwritten by the new setQSO + // Just 10ms did not seemed to be enough in tests, but 50ms is fine. + + console.log('checking multiQSOs') + console.log(multiQSOs) + + dispatch(addQSOs({ uuid: operation.uuid, qsos: multiQSOs })) + if (DEBUG) logTimer('submit', 'handleSubmit added QSOs') + + // logging is done at this point. we can navigate away from popup + navigation.goBack() + }, 50) + + // if (DEBUG) + // logTimer('submit', 'handleSubmit after setQSO') + } + }, 0) + }, [dispatch, doSubmit, isValidQSO, operation, ourInfo?.call, qsos, route.params.qso, settings, vfo, navigation]) + + return ( + + Log Spot? + {route.params.qso.band} : {route.params.qso.mode} + {/* {route.params.qso.their.call}{route.params.qso.their.guess.emoji} */} + + {route.params.qso.their?.call ?? '?'} + + {guess?.name} + {guess?.note && + <> + {guess?.note} + + } + {route.params.qso.spot.label} + + + { + // this triggers the log code above and navigates back to the spots list + handleSubmit() + }} + rippleColor='rgba(0, 255, 255, .32)' + > + Log it! + + navigation.goBack()} + rippleColor='rgba(218, 68, 3, 0.32)' + > + Cancel + + + + ) +} diff --git a/src/screens/OperationScreens/OpSpotsTab/OpSpotsTab.jsx b/src/screens/OperationScreens/OpSpotsTab/OpSpotsTab.jsx index dac8ac44..145aff6d 100644 --- a/src/screens/OperationScreens/OpSpotsTab/OpSpotsTab.jsx +++ b/src/screens/OperationScreens/OpSpotsTab/OpSpotsTab.jsx @@ -42,7 +42,23 @@ export default function OpSpotsTab ({ navigation, route }) { } }, [navigation, route?.params, extraSpotInfoHooks, dispatch, online, settings]) + const handleLongPress = useCallback(async ({ spot }) => { + if (spot._ourSpot) return + + for (const hook of extraSpotInfoHooks) { + await hook.extraSpotInfo({ online, settings, dispatch, spot }) + } + + if (settings.bigThumbsMode === true) { + if (route?.params?.splitView) { + navigation.navigate('Operation', { ...route?.params, qso: { ...spot, our: undefined, _suggestedKey: spot.key, key: undefined } }) + } else { + navigation.navigate('OpSpotModal', { operation, qso: { ...spot, our: undefined, _suggestedKey: spot.key, key: undefined } }) + } + } + }, [navigation, route?.params, extraSpotInfoHooks, dispatch, online, settings, operation]) + return ( - + ) } diff --git a/src/screens/OperationScreens/OpSpotsTab/components/BigThumbsSpotItem.jsx b/src/screens/OperationScreens/OpSpotsTab/components/BigThumbsSpotItem.jsx new file mode 100644 index 00000000..e0f7cc45 --- /dev/null +++ b/src/screens/OperationScreens/OpSpotsTab/components/BigThumbsSpotItem.jsx @@ -0,0 +1,153 @@ +/* + * Copyright ©️ 2025 Cainan Whelchel + * + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import React, { useMemo } from 'react' +import { Icon, Text } from 'react-native-paper' + +import { View } from 'react-native' +import { partsForFreqInMHz } from '../../../../tools/frequencyFormats' +import { fmtDateTimeRelative, prepareTimeValue } from '../../../../tools/timeFormats' +import { paperNameOrHam2KIcon, H2kPressable } from '../../../../ui' + +/** + * When Big Thumbs Mode is enabled, this is used to render spots in SpotList. + * + * It's the same as SpotItem but with some padding and different layout for better viewing + * while the device is further away from the user. + */ +const BigThumbsSpotItem = React.memo(function QSOItemMobile ({ spot, styles, onPress, onLongPress }) { + const freqParts = useMemo(() => partsForFreqInMHz(spot.freq), [spot.freq]) + + if (spot?.their?.call === 'W8WR') spot.their.call = 'N2Y' + + const { commonStyle, modeStyle, refStyle, callStyle } = useMemo(() => { + const workedStyles = {} + if (spot.spot?.type === 'self') { + workedStyles.commonStyle = { + color: styles.colors.tertiary, + opacity: 0.7 + } + } + if (spot.spot?.type === 'duplicate') { + workedStyles.commonStyle = { + textDecorationLine: 'line-through', + textDecorationColor: styles.colors.onBackground, + opacity: 0.6 + } + } + // no band on big thumbs spot item + // if (spot.spot?.flags?.newBand) { + // workedStyles.bandStyle = { + // fontWeight: 'bold', + // color: styles.colors.important + // } + // } + if (spot.spot?.flags?.newMode) { + workedStyles.modeStyle = { + fontWeight: 'bold', + color: styles.colors.important + } + } + if (spot.spot?.flags?.specialCall) { + workedStyles.callStyle = { + color: styles.colors.bands['40m'] + } + workedStyles.refStyle = { + color: styles.colors.bands['40m'] + } + } + if (spot.spot?.flags?.newRef || spot.spot?.flags?.newDay) { + workedStyles.refStyle = { + fontWeight: 'bold', + color: styles.colors.important + } + } + if (spot.spot?.flags?.newMult) { + workedStyles.callStyle = { + fontWeight: 'bold', + color: styles.colors.bands['10m'] + } + workedStyles.refStyle = { + fontWeight: 'bold', + color: styles.colors.bands['10m'] + } + } + + return workedStyles + }, [spot, styles]) + + function getTimeColor (millis) { + const t1 = prepareTimeValue(millis) + const t2 = prepareTimeValue(new Date()) + + if (t1 && t2) { + const diff = t2 - t1 + + if (diff > (20 * 60 * 1000)) { + return styles.bigThumbs.time.oldest + } else if (diff > (15 * 60 * 1000)) { + return styles.bigThumbs.time.old + } else if (diff <= (2 * 60 * 1000)) { + return styles.bigThumbs.time.new + } + } + return styles.bigThumbs.time.normal + }; + + return ( + onPress && onPress({ spot })} + onLongPress={() => onLongPress && onLongPress({ spot })} + rippleColor='rgba(0, 255, 255, .32)' + > + + + + + {freqParts[0]} + + + .{freqParts[1]} + {freqParts[2] !== '000' && ( + .{freqParts[2]} + )} + + + + + + + {spot.their?.call ?? '?'} + {spot.their?.guess?.emoji && ( + {spot.their?.guess?.emoji} + )} + + {fmtDateTimeRelative(spot.spot?.timeInMillis, { roundTo: 'minutes' })} + + + {spot.mode} + {spot.spots.filter(s => s?.icon).map(subSpot => ( + + + + ))} + + {spot.spot.emoji} + {spot.spot.label} + + + + + + ) +}) +export default BigThumbsSpotItem diff --git a/src/screens/OperationScreens/OpSpotsTab/components/SpotItem.jsx b/src/screens/OperationScreens/OpSpotsTab/components/SpotItem.jsx index 97fbcbbc..9e4f1f97 100644 --- a/src/screens/OperationScreens/OpSpotsTab/components/SpotItem.jsx +++ b/src/screens/OperationScreens/OpSpotsTab/components/SpotItem.jsx @@ -13,7 +13,10 @@ import { partsForFreqInMHz } from '../../../../tools/frequencyFormats' import { fmtDateTimeRelative } from '../../../../tools/timeFormats' import { paperNameOrHam2KIcon, H2kPressable } from '../../../../ui' -const SpotItem = React.memo(function QSOItem({ spot, onPress, styles, extendedWidth }) { +export function guessItemHeight (qso, styles) { + return styles.doubleRow.height + styles.doubleRow.borderBottomWidth +} +const SpotItem = React.memo(function QSOItem ({ spot, onPress, styles, extendedWidth, settings }) { const freqParts = useMemo(() => partsForFreqInMHz(spot.freq), [spot.freq]) if (spot?.their?.call === 'W8WR') spot.their.call = 'N2Y' @@ -74,7 +77,9 @@ const SpotItem = React.memo(function QSOItem({ spot, onPress, styles, extendedWi }, [spot, styles]) return ( - onPress && onPress({ spot })}> + onPress && onPress({ spot })} + > diff --git a/src/screens/OperationScreens/OpSpotsTab/components/SpotList.jsx b/src/screens/OperationScreens/OpSpotsTab/components/SpotList.jsx index 3d298fd0..2b5580aa 100644 --- a/src/screens/OperationScreens/OpSpotsTab/components/SpotList.jsx +++ b/src/screens/OperationScreens/OpSpotsTab/components/SpotList.jsx @@ -14,9 +14,10 @@ import getItemLayout from 'react-native-get-item-layout-section-list' import { useThemedStyles } from '../../../../styles/tools/useThemedStyles' import SpotItem from './SpotItem' +import BigThumbsSpotItem from './BigThumbsSpotItem' import SpotHeader from './SpotHeader' -export default function SpotList ({ sections, loading, refresh, style, onPress }) { +export default function SpotList ({ sections, loading, refresh, style, onPress, onLongPress, settings }) { const styles = useThemedStyles(_prepareStyles, style) const safeArea = useSafeAreaInsets() @@ -42,9 +43,14 @@ export default function SpotList ({ sections, loading, refresh, style, onPress } const renderRow = useCallback(({ item, index }) => { const spot = item return ( - + (settings.bigThumbsMode ? ( + + ) : ( + + ) + ) ) - }, [styles, onPress, extendedWidth, paddingRight, paddingLeft]) + }, [onPress, onLongPress, styles, paddingRight, paddingLeft, extendedWidth, settings]) // eslint-disable-next-line react-hooks/exhaustive-deps const calculateLayout = useCallback( @@ -79,7 +85,7 @@ export default function SpotList ({ sections, loading, refresh, style, onPress } ) } -function _prepareStyles (themeStyles, style) { +function _prepareStyles (themeStyles, style, deviceColorScheme) { const DEBUG = false const commonStyles = { @@ -88,6 +94,16 @@ function _prepareStyles (themeStyles, style) { borderWidth: DEBUG ? 1 : 0 } + const bigThumbsStyles = { + fontSize: themeStyles.normalFontSize * 1.2, + lineHeight: themeStyles.normalFontSize * 1.5, + borderWidth: 0 // debug + } + + // console.log(themeStyles) + // console.log(style) + // console.log(deviceColorScheme) + return { ...themeStyles, doubleRow: { @@ -103,6 +119,63 @@ function _prepareStyles (themeStyles, style) { paddingRight: 0, justifyContent: 'center' }, + bigThumbs: { + freq: { + ...bigThumbsStyles, + ...themeStyles.text.numbers, + ...themeStyles.text.lighter, + flexDirection: 'column', + width: themeStyles.oneSpace * 11.15, + textAlign: 'center', + alignItems: 'center' + }, + freqMHz: { + ...bigThumbsStyles, + fontWeight: '600', + textAlign: 'right', + fontSize: themeStyles.normalFontSize * 1.0 + }, + freqKHz: { + ...bigThumbsStyles, + textAlign: 'right', + fontWeight: '700' + }, + freqHz: { + ...bigThumbsStyles, + fontWeight: '600', + textAlign: 'right', + fontSize: themeStyles.normalFontSize + }, + call: { + ...bigThumbsStyles + }, + label: { + flex: 1, + fontSize: themeStyles.normalFontSize * 0.9 + }, + mode: { + fontSize: themeStyles.normalFontSize * 0.9, + flex: 0, + marginLeft: themeStyles.oneSpace * 0.5, + width: themeStyles.oneSpace * 4, + textAlign: 'right', + marginRight: themeStyles.oneSpace * 1.0 + }, + time: { + oldest: { + color: (themeStyles.theme.dark) ? '#f11818ff' : '#e70606ff' + }, + old: { + color: (themeStyles.theme.dark) ? '#ff733fff' : '#9a4e0cff' + }, + normal: { + color: (themeStyles.theme.dark) ? '#CCC' : '#222' + }, + new: { + color: (themeStyles.theme.dark) ? '#11dda0ff' : '#128700ff' + } + } + }, fields: { freq: { ...commonStyles, diff --git a/src/screens/OperationScreens/OpSpotsTab/components/SpotsPanel.jsx b/src/screens/OperationScreens/OpSpotsTab/components/SpotsPanel.jsx index 99701a3b..d1c2e9f5 100644 --- a/src/screens/OperationScreens/OpSpotsTab/components/SpotsPanel.jsx +++ b/src/screens/OperationScreens/OpSpotsTab/components/SpotsPanel.jsx @@ -66,7 +66,7 @@ function prepareStyles (baseStyles, themeColor, style) { } } -export default function SpotsPanel ({ operation, qsos, sections, onSelect, style }) { +export default function SpotsPanel ({ operation, qsos, sections, onSelect, style, onLongPress }) { const themeColor = 'tertiary' const styles = useThemedStyles(prepareStyles, themeColor, style) @@ -316,6 +316,10 @@ export default function SpotsPanel ({ operation, qsos, sections, onSelect, style onSelect && onSelect({ spot }) }, [onSelect]) + const handleLongPress = useCallback(({ spot }) => { + onLongPress && onLongPress({ spot }) + }, [onLongPress]) + return ( {showControls ? ( @@ -375,6 +379,8 @@ export default function SpotsPanel ({ operation, qsos, sections, onSelect, style loading={spotsState.loading} refresh={refresh} onPress={handlePress} + onLongPress={handleLongPress} + settings={settings} style={{ paddingBottom: style?.paddingBottom, paddingRight: style?.paddingRight, diff --git a/src/screens/SettingsScreens/screens/CreditsSettingsScreen.jsx b/src/screens/SettingsScreens/screens/CreditsSettingsScreen.jsx index 008ef472..f90397cc 100644 --- a/src/screens/SettingsScreens/screens/CreditsSettingsScreen.jsx +++ b/src/screens/SettingsScreens/screens/CreditsSettingsScreen.jsx @@ -120,6 +120,12 @@ export default function CreditsSettingsScreen ({ navigation, splitView }) { leftIcon="account" onPress={() => navigation.navigate('CallInfo', { call: 'W8NI' })} /> + navigation.navigate('CallInfo', { call: 'N9FZ' })} + /> diff --git a/src/screens/SettingsScreens/screens/LoggingSettingsScreen.jsx b/src/screens/SettingsScreens/screens/LoggingSettingsScreen.jsx index f5c5f799..5a5a46fe 100644 --- a/src/screens/SettingsScreens/screens/LoggingSettingsScreen.jsx +++ b/src/screens/SettingsScreens/screens/LoggingSettingsScreen.jsx @@ -55,6 +55,15 @@ export default function LoggingSettingsScreen ({ navigation, splitView }) { onPress={() => dispatch(setSettings({ leftieMode: !settings.leftieMode }))} /> + {settings.devMode && ( + dispatch(setSettings({ bigThumbsMode: value }))} + onPress={() => dispatch(setSettings({ bigThumbMode: !settings.bigThumbsMode }))} + /> + )} setCurrentDialog('')} /> )} + { justifyContent: 'space-between', width: '100%' }, + doubleRowMobileMode: { + height: oneSpace * 10, + borderBottomWidth: 1, + borderBottomColor: theme.colors.outline, + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + }, doubleRowInnerRow: { // borderWidth: 1, height: oneSpace * 2.6, flexDirection: 'row', width: '100%' }, + doubleRowInnerRowMobile: { + //borderWidth: 1, + height: oneSpace * 2.8, + flexDirection: 'row', + width: '80%' + }, + doubleRowMobileModeLeft: { + //borderWidth: 1, + height: '100%', + flexDirection: 'row', + width:'20%', + alignItems: 'center' + }, + doubleRowMobileModeRight: { + //borderWidth: 1, + height: '100%', + flexDirection: 'column', + width:'100%', + justifyContent: 'center' + }, rowText: { fontSize: normalFontSize, fontFamily: 'Roboto',