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
7 changes: 3 additions & 4 deletions native/src/BottomTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Text from './components/base/Text'
import { TAB_NAVIGATOR_ID } from './constants'
import { NavigationProps, RoutesParamsType } from './constants/NavigationTypes'
import buildConfig from './constants/buildConfig'
import dimensions from './constants/dimensions'
import useLoadRegionContent from './hooks/useLoadRegionContent'
import useNavigate from './hooks/useNavigate'
import useRegionAppContext from './hooks/useRegionAppContext'
Expand All @@ -44,8 +45,6 @@ const PlacesStack = createStackNavigator<RoutesParamsType>()
const EventsStack = createStackNavigator<RoutesParamsType>()
const NewsStack = createStackNavigator<RoutesParamsType>()

const TAB_HEIGHT = 60

const CategoriesStackScreen = () => (
<CategoriesStack.Navigator screenOptions={{ header: defaultHeader, animation: 'none' }}>
<CategoriesStack.Screen name={CATEGORIES_ROUTE} initialParams={{}} component={CategoriesContainer} />
Expand Down Expand Up @@ -188,15 +187,15 @@ const BottomTabNavigator = ({ navigation }: BottomTabNavigatorProps): ReactEleme
tabBarActiveTintColor: theme.colors.onSurface,
tabBarInactiveTintColor: theme.colors.onSurfaceVariant,
tabBarStyle: {
height: TAB_HEIGHT + insets.bottom,
height: dimensions.bottomNavigationHeight + insets.bottom,
backgroundColor: theme.colors.surfaceVariant,
display: bottomTabsVisible ? 'flex' : 'none',
},
sceneStyle: bottomTabsVisible ? undefined : { paddingBottom: insets.bottom },
}}>
{Tabs}
</Tab.Navigator>
{chatVisible && <ChatFab style={{ bottom: TAB_HEIGHT + insets.bottom }} />}
{chatVisible && <ChatFab style={{ bottom: dimensions.bottomNavigationHeight + insets.bottom }} />}
</View>
)
}
Expand Down
33 changes: 23 additions & 10 deletions native/src/components/ChatFab.tsx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's hide the chatFab when the Tts player is open because it's under the player and can't be open anyway.

@lunars97 lunars97 Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's ask @ulyanahoy-cloud, whether she has other ideas? @ulyanahoy-cloud what do you think about removing chat button or fitting them together by making the dialog smaller when the read aloud player is activated? Currently chat button is underneath of the read aloud player dialog 💭
image

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useFocusEffect } from '@react-navigation/native'
import React, { ReactElement, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { ViewStyle } from 'react-native'
import { Badge, FAB, useTheme } from 'react-native-paper'
import { TouchableOpacity, ViewStyle } from 'react-native'
import { Badge, useTheme } from 'react-native-paper'
import styled from 'styled-components/native'

import { CHAT_DEFAULT_POLLING_INTERVAL, CHAT_ROUTE, getChatName } from 'shared'
Expand All @@ -12,7 +12,9 @@ import buildConfig from '../constants/buildConfig'
import useNavigate from '../hooks/useNavigate'
import useRegionAppContext from '../hooks/useRegionAppContext'
import { determineApiUrl } from '../utils/helpers'
import { elevatedStyle } from '../utils/styles'
import ChatHighlightPopup from './ChatHighlightPopup'
import Icon from './base/Icon'

const Container = styled.View`
position: absolute;
Expand All @@ -21,6 +23,16 @@ const Container = styled.View`
align-items: flex-end;
`

const TouchableFab = styled(TouchableOpacity)`
width: 56px;
height: 56px;
border-radius: 30%;
background-color: ${props => props.theme.colors.primary};
justify-content: center;
align-items: center;
${elevatedStyle}
`

const StyledBadge = styled(Badge)`
position: absolute;
right: -8px;
Expand Down Expand Up @@ -60,17 +72,18 @@ const ChatFab = ({ style }: ChatFabProps): ReactElement => {
return (
<Container style={style}>
<ChatHighlightPopup chatName={getChatName(buildConfig().appName)} />
<FAB
icon='forum-outline'
<TouchableFab
accessibilityRole='button'
activeOpacity={0.9}
onPress={() => navigation.navigate(CHAT_ROUTE)}
accessibilityLabel={getChatName(buildConfig().appName)}
variant='primary'
style={{ backgroundColor: theme.colors.primary }}
size='medium'
aria-label={getChatName(buildConfig().appName)}
/>
testID='fab'>
<Icon size={24} source='forum-outline' color={theme.colors.background} />
</TouchableFab>
{unreadMessageCount > 0 && (
<StyledBadge aria-label={t('unreadMessages', { count: unreadMessageCount })}>{unreadMessageCount}</StyledBadge>
<StyledBadge accessibilityLabel={t('unreadMessages', { count: unreadMessageCount })}>
{unreadMessageCount}
</StyledBadge>
)}
</Container>
)
Expand Down
8 changes: 6 additions & 2 deletions native/src/components/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const Feedback = ({

if (sendingStatus === 'successful') {
return (
<Wrapper>
<Wrapper accessible accessibilityRole='alert' accessibilityLiveRegion='assertive'>
<Caption title={t('thanksHeadline')} />
<Text>{t('thanksMessage')}</Text>
<Button onPress={navigation.goBack} mode='contained' style={{ marginTop: 16 }}>
Expand Down Expand Up @@ -106,7 +106,11 @@ const Feedback = ({
accessibilityRole='text'
/>
{sendingStatus === 'failed' && (
<Text variant='body2' style={{ textAlign: 'left' }}>
<Text
variant='body2'
style={{ textAlign: 'left' }}
accessibilityRole='alert'
accessibilityLiveRegion='assertive'>
{t('failedSendingFeedback')}
</Text>
)}
Expand Down
1 change: 1 addition & 0 deletions native/src/components/HeaderBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const HeaderBox = ({ goBack, canGoBack = true, title, language, regionsPath }: H
const AppIcon = buildConfigAssets().AppIcon
const HeaderIcon = canGoBack ? (
<Appbar.BackAction
accessibilityRole='button'
style={{ backgroundColor: 'transparent' }}
onPress={goBack}
accessibilityLabel={t('back')}
Expand Down
1 change: 1 addition & 0 deletions native/src/components/HeaderMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const HeaderMenu = ({
iconColor={theme.colors.onSurface}
onPress={() => setVisible(!visible)}
accessibilityLabel={t('settings')}
accessibilityState={{ expanded: visible }}
testID='header-overflow-menu-button'
/>
}>
Expand Down
2 changes: 1 addition & 1 deletion native/src/components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Container = styled.View<{ $padding: boolean }>`
${props => props.$padding && `padding: 0 ${dimensions.pageContainerPaddingHorizontal}px 8px;`}
`
const SpaceForTts = styled.View<{ $ttsPlayerVisible: boolean }>`
height: ${props => (props.$ttsPlayerVisible ? dimensions.ttsPlayerHeight : 0)}px;
height: ${props => (props.$ttsPlayerVisible ? dimensions.ttsPlayerHeight + dimensions.bottomNavigationHeight : 0)}px;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a space at the bottom at aschaffenburg:
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching it 🙈

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I dont have any solution for this, except for removing additional height and leaving it to cover the last update date 🤔

@bahaaTuffaha bahaaTuffaha Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I think the issue is not from here its from the BottomTabNavigator.tsx

sceneStyle: bottomTabsVisible
            ? undefined
            : { paddingBottom: insets.bottom + dimensions.bottomNavigationHeight },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I mean this. I added it to add a height to not cover the last update date, but then it breaks the whole view when there are no navigation tabs...

@bahaaTuffaha bahaaTuffaha Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of 2 possible solutions for the spacing at aschaffenburg:

  1. at TtsPlayer.tsx
  insetBottom={bottom + (bottomTabsVisible ? dimensions.bottomNavigationHeight : 0)}

now the difficulty is where can we get the bottomTabsVisible:

const { regionCode } = useAppContext()
const { data: regions } = useLoadRegions()

 const region = regions?.find(it => it.code === regionCode)
const bottomTabsVisible = !!(
  region?.placesEnabled ||
  region?.localNewsEnabled ||
  region?.tuNewsEnabled ||
  region?.eventsEnabled
)
  1. Or create a custom hook for bottom tab navigation visibility that has a context.
  2. OR maybe buildConfig().featureFlags.fixedRegion but a fixed region could have a bottom nav right?

`

type PageProps = {
Expand Down
1 change: 1 addition & 0 deletions native/src/components/SearchHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const SearchHeader = ({ query, navigation, onSearchChanged }: SearchHeaderProps)
<BoxShadow>
<Horizontal>
<Appbar.BackAction
accessibilityRole='button'
onPress={navigation.goBack}
accessibilityLabel={t('back')}
style={{ backgroundColor: 'transparent' }}
Expand Down
14 changes: 4 additions & 10 deletions native/src/components/TtsPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,15 @@ import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import { TouchableRipple } from 'react-native-paper'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import styled, { css, useTheme } from 'styled-components/native'
import styled, { useTheme } from 'styled-components/native'

import { isRTLText } from '../constants/contentDirection'
import dimensions from '../constants/dimensions'
import { elevatedStyle } from '../utils/styles'
import Icon from './base/Icon'
import IconButton from './base/IconButton'
import Text from './base/Text'

const elevatedStyle = css`
shadow-color: ${props => props.theme.colors.onSurface};
shadow-offset: 0 2px;
shadow-opacity: 0.2;
shadow-radius: 3px;
elevation: 5;
`

const StyledTtsPlayer = styled.View<{ insetBottom: number }>`
background-color: ${props => props.theme.colors.ttsPlayer.background};
border-radius: 8px;
Expand Down Expand Up @@ -91,7 +85,7 @@ const TtsPlayer = ({
const { t } = useTranslation('layout')

return (
<StyledTtsPlayer insetBottom={bottom}>
<StyledTtsPlayer insetBottom={dimensions.bottomNavigationHeight + bottom} accessibilityLabel={t('readAloudPlayer')}>
<TouchableRipple
borderless
role='button'
Expand Down
7 changes: 6 additions & 1 deletion native/src/components/base/ToggleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ const ToggleButton = ({ text, onPress, icon, active, style }: ToggleButtonProps)
},
style,
]}>
<TouchableRipple role='switch' onPress={onPress} style={styles.TouchableRippleStyle} borderless>
<TouchableRipple
role='button'
Comment thread
lunars97 marked this conversation as resolved.
onPress={onPress}
style={styles.TouchableRippleStyle}
borderless
accessibilityState={{ selected: active }}>
<>
{icon}
<Text variant='body3' numberOfLines={1} style={[styles.text, { color: getTextColor() }]}>
Expand Down
2 changes: 2 additions & 0 deletions native/src/constants/dimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type DimensionsType = {
headerHeight: number
modalHeaderHeight: number
ttsPlayerHeight: number
bottomNavigationHeight: number
categoryListItem: {
iconSize: number
margin: number
Expand All @@ -23,6 +24,7 @@ const dimensions: DimensionsType = {
headerHeight: 60,
modalHeaderHeight: 40,
ttsPlayerHeight: 100,
bottomNavigationHeight: 60,
categoryListItem: {
iconSize: 40,
margin: 5,
Expand Down
1 change: 1 addition & 0 deletions native/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const ROOT_NAVIGATOR_ID = 'rootNavigator'

export const TAB_NAVIGATOR_ID = 'tabNavigator'
export type NavigatorIds = typeof ROOT_NAVIGATOR_ID | typeof TAB_NAVIGATOR_ID

Expand Down
5 changes: 4 additions & 1 deletion native/src/routes/LanguageSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { shouldPolyfill } from '@formatjs/intl-displaynames/should-polyfill'
import '@formatjs/intl-locale/polyfill'
import React, { ReactElement, useCallback, useContext, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'
import { AccessibilityInfo, StyleSheet } from 'react-native'
import { Button } from 'react-native-paper'
import styled from 'styled-components/native'

Expand Down Expand Up @@ -77,6 +77,9 @@ const LanguageSelection = ({ navigation, route }: LanguageSelectionProps): React
if (code !== languageCode) {
changeLanguageCode(code)
}
AccessibilityInfo.announceForAccessibility(
`${t('languageChangedTo')} ${userLanguageNames?.of(code) ?? name}`,
)
navigation.goBack()
}
: () => setAlertDialogTitle(t('noTranslation')),
Expand Down
6 changes: 3 additions & 3 deletions native/src/routes/__tests__/Places.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('Places', () => {
it('should not push to history when a category is selected inside the filter modal', () => {
const { localHistory, getByRole } = renderPlaces({ ...resetHistory, showFilterSelection: true })

fireEvent.press(getByRole('switch', { name: 'Dienstleistung' }))
fireEvent.press(getByRole('button', { name: 'Dienstleistung' }))

expect(localHistory.push).not.toHaveBeenCalled()
expect(localHistory.pushReset).not.toHaveBeenCalled()
Expand All @@ -193,7 +193,7 @@ describe('Places', () => {
it('should apply the newly selected category when showPlaces is pressed', () => {
const { localHistory, getByRole, getByText } = renderPlaces({ ...resetHistory, showFilterSelection: true })

fireEvent.press(getByRole('switch', { name: 'Dienstleistung' }))
fireEvent.press(getByRole('button', { name: 'Dienstleistung' }))
fireEvent.press(getByText('showPlaces'))

expect(localHistory.pop).toHaveBeenCalledTimes(1)
Expand Down Expand Up @@ -248,7 +248,7 @@ describe('Places', () => {
const initial = renderAt({ ...resetHistory, showFilterSelection: true })
const { getByRole, getByText, getAllByText, rerender } = renderWithTheme(initial.element)

fireEvent.press(getByRole('switch', { name: 'Gastronomie' }))
fireEvent.press(getByRole('button', { name: 'Gastronomie' }))
fireEvent.press(getByText('showPlaces'))
expect(initial.localHistory.pushReset).toHaveBeenLastCalledWith({
placeCategoryId: place0.category.id,
Expand Down
9 changes: 9 additions & 0 deletions native/src/utils/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css } from 'styled-components/native'

export const elevatedStyle = css`
shadow-color: ${props => props.theme.colors.onSurface};
shadow-offset: 0 2px;
shadow-opacity: 0.2;
shadow-radius: 3px;
elevation: 5;
`
6 changes: 6 additions & 0 deletions translations/src/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -7596,6 +7596,7 @@
"localInformationLabel": "Informationen",
"events": "Veranstaltungen",
"news": "Nachrichten",
"languageChangedTo": "Sprache wurde geändert zu",
"noLanguages": "Nicht verfügbar in anderen Sprachen",
"noTranslation": "Keine Übersetzung verfügbar",
"languageNotAvailableMessage": "Diese Seite ist noch nicht in allen Sprachen verfügbar. Wir erweitern unser Sprachangebot aktiv. Du kannst deine Sprache über die <1>Feedback-Funktion</1> anfragen.",
Expand Down Expand Up @@ -7623,6 +7624,7 @@
"getOnPlayStore": "Im Google Play Store anschauen",
"accessibility": "Barrierefreiheit",
"readAloud": "Vorlesefunktion",
"readAloudPlayer": "Vorlese-Player",
"nothingToRead": "Nichts zu lesen",
"nothingToReadFullMessage": "Auf dieser Seite gibt es nichts zu lesen.",
"languageNotSupported": "Sprache wird nicht unterstützt",
Expand Down Expand Up @@ -7912,6 +7914,7 @@
"localInformationLabel": "Information",
"events": "Events",
"news": "News",
"languageChangedTo": "Language changed to",
"noLanguages": "Not available in other languages",
"noTranslation": "No translation available",
"languageNotAvailableMessage": "This page is not yet available in all languages. We are actively expanding our language options. You can request your language via the <1>feedback</1> feature.",
Expand Down Expand Up @@ -7939,6 +7942,7 @@
"getOnPlayStore": "GET — On the Google Play Store",
"accessibility": "Accessibility",
"readAloud": "Read aloud",
"readAloudPlayer": "Read aloud player",
"nothingToRead": "Nothing to read",
"nothingToReadFullMessage": "There is nothing to read on this page.",
"languageNotSupported": "Language is not supported.",
Expand Down Expand Up @@ -8727,6 +8731,7 @@
"searchLanguage": "Поиск языка",
"locations": "Карта",
"changeLocation": "Изменить местоположение",
"languageChangedTo": "Язык изменен на",
"changeLanguage": "Изменить язык",
"settings": "Настройки",
"copyUrl": "Копировать ссылку",
Expand All @@ -8745,6 +8750,7 @@
"getOnPlayStore": "Просмотреть в Google Play Store",
"accessibility": "Доступность",
"readAloud": "Функция чтения вслух",
"readAloudPlayer": "Проигрыватель для чтения вслух",
"nothingToRead": "Текст для чтения отсутствует",
"nothingToReadFullMessage": "На этой странице отсутствует текст для чтения.",
"languageNotSupported": "Язык не поддерживается",
Expand Down
11 changes: 10 additions & 1 deletion web/src/components/GeneralHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { useLocation } from 'react-router'
import { REGIONS_ROUTE, pathnameFromRouteInformation } from 'shared'
import { LanguageModel } from 'shared/api'

import useAnnounceLanguageChange from '../hooks/useAnnounceLanguageChange'
import { supportedLanguages } from '../utils'
import Header from './Header'
import HeaderLanguageSelectorItem from './HeaderLanguageSelectorItem'
import HeaderMenu from './HeaderMenu'
import LiveAnnouncer from './LiveAnnouncer'
import MenuItem from './MenuItem'

type GeneralHeaderProps = {
Expand All @@ -30,6 +32,8 @@ const GeneralHeader = ({ languageCode, regionLanguages }: GeneralHeaderProps): R
path: `/${slug}/${language.code}`,
}))

const announcement = useAnnounceLanguageChange(languageCode, languageChangePaths, t)

const actionItems = [
languageChangePaths.length > 0 ? (
<HeaderLanguageSelectorItem
Expand All @@ -44,7 +48,12 @@ const GeneralHeader = ({ languageCode, regionLanguages }: GeneralHeaderProps): R
</HeaderMenu>,
]

return <Header logoHref={regionsPath} actionItems={actionItems} language={languageCode} />
return (
<>
<LiveAnnouncer message={announcement} />
<Header logoHref={regionsPath} actionItems={actionItems} language={languageCode} />
</>
)
}

export default GeneralHeader
1 change: 1 addition & 0 deletions web/src/components/HeaderLanguageSelectorItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const HeaderLanguageSelectorItem = ({
text={isOpen ? '' : t('changeLanguage') /* to not cover the dropdown with the tooltip */}
icon={<TranslateOutlinedIcon />}
innerText={forceText || desktop ? currentLanguageName : undefined}
aria-expanded={isOpen}
/>
)

Expand Down
9 changes: 7 additions & 2 deletions web/src/components/HeaderMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,15 @@ const HeaderMenu = ({ children, pageTitle, fitScreen, ref }: HeaderMenuProps): R

return (
<>
<IconButton onClick={openMenu} aria-label={t('sideBarOpenAriaLabel')} aria-expanded={open}>
<IconButton
onClick={openMenu}
aria-label={t('sideBarOpenAriaLabel')}
aria-expanded={open}
aria-haspopup='menu'
aria-controls={open ? 'side-menu' : undefined}>
<MoreVertIcon />
</IconButton>
<StyledMenu anchorEl={menuAnchorElement} open={open} onClose={closeMenu}>
<StyledMenu role='menu' id='side-menu' anchorEl={menuAnchorElement} open={open} onClose={closeMenu}>
{withDividers([
...items,
<MenuItem
Expand Down
Loading
Loading