Skip to content

Commit

Permalink
2916: Add tts on android (#2950)
Browse files Browse the repository at this point in the history
* 2916: tts Part1 modifying the base text component

* 2916: tts for webView Part 2

* Improving tts and creating TtsPlayer component

* 2916: Adding Tests for TtsPlayer

* 2916: tts creating the UI

* 2916: tts UI part 2

* 2916: improvements to reading and volume changing

* 2916: improvements and code is moved to app level as a wrapper

* 2916: Preventing tts from running on wrong screens

* 2916: Added translation and enhancements to next and prev sentence reading

* 2916: Stopping tts in background

* 2916: Fixing TtsPlayer.spec and adding a mock object

* 2916: Requested changes: fixing SVGs and changing the way it extracts sentences from html

* 2916: updating yarn.lock

* 2916: Requested changes: fixing crashes and optimizing SVGs

* 2916: Fixing TtsPlayer.spec missing react-native-reanimated mock function

* 2916: Requested Changes: adjusting UI and removing duplicate code

* 2916: Adding tts for FeatureFlagsType

* 2916: Requested Changes for splitting logic and using parseHTML

* 2916: Removed Volume slider for tts

* 2916: Requested Changes for removing unnecessary lines and replacing useEffect with StartReading

* 2916: Replacing TouchableOpacity with Pressable

* 2916: Adding more languages to the unsupportedLanguagesForTts list

* 2916: requested Changes: refactoring TtsContainer

* 2916: Refactoring part 2

* 2916: Refactoring part 3

* 2916: Removing unnessasery dependency from useTtsPlayer's useEffect

* 2916: TTS android proposal (#3038)

* 2916: Simplify code and improvements

* 2916: adding boolean to buildConfig featureFlags tts

---------

Co-authored-by: bahaa <[email protected]>

* 2916: fixing language is not changed and fixing unavailable reading when changing language and adding snackBar

* 2916: requested changes and using better naming

* 2916: Fixing truncate function when passing long words

* 2916: requested changes for header item and elevation styling

* 2916: Android platform needed mocking

---------

Co-authored-by: Steffen Kleinle <[email protected]>
  • Loading branch information
bahaaTuffaha and steffenkleinle authored Jan 28, 2025
1 parent 83e715d commit 08e3ab8
Show file tree
Hide file tree
Showing 33 changed files with 580 additions and 16 deletions.
1 change: 1 addition & 0 deletions assets/icons/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/playback.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions build-configs/BuildConfigType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type FeatureFlagsType = FixedCityType & {
cityNotCooperating?: boolean
cityNotCooperatingTemplate: string | null
chat: boolean
tts: boolean
}

// Available on all platforms
Expand Down
1 change: 1 addition & 0 deletions build-configs/aschaffenburg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const commonAschaffenburgBuildConfig: CommonBuildConfigType = {
fixedCity: 'hallo',
cityNotCooperatingTemplate: null,
chat: false,
tts: false,
},
aboutUrls: {
default: 'https://www.aschaffenburg.de/halloaschaffenburg',
Expand Down
4 changes: 4 additions & 0 deletions build-configs/common/theme/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type ColorsType = {
warningColor: string
linkColor: string
themeContrast: string
grayBackgroundColor: string
slightlyDarkGray: string
}
export const commonLightColors = {
backgroundAccentColor: '#fafafa',
Expand All @@ -34,4 +36,6 @@ export const commonLightColors = {
invalidInput: '#B3261E',
warningColor: '#FFA726',
linkColor: '#0b57d0',
grayBackgroundColor: '#dedede',
slightlyDarkGray: '#b9b9b9',
}
1 change: 1 addition & 0 deletions build-configs/integreat-e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const integreatE2e = {
fixedCity: null,
cityNotCooperatingTemplate,
chat: false,
tts: false,
},
}
const commonIntegreatE2eBuildConfig: CommonBuildConfigType = {
Expand Down
1 change: 1 addition & 0 deletions build-configs/integreat-test-cms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const integreatTestCms = {
fixedCity: null,
cityNotCooperatingTemplate,
chat: true,
tts: true,
},
}
export const commonIntegreatTestCmsBuildConfig: CommonBuildConfigType = {
Expand Down
1 change: 1 addition & 0 deletions build-configs/integreat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const commonIntegreatBuildConfig: CommonBuildConfigType = {
fixedCity: null,
cityNotCooperatingTemplate,
chat: true,
tts: false,
},
aboutUrls: {
default: 'https://integreat-app.de/about/',
Expand Down
1 change: 1 addition & 0 deletions build-configs/malte-test-cms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const commonMalteTestCmsBuildConfig: CommonBuildConfigType = {
fixedCity: null,
cityNotCooperatingTemplate: null,
chat: false,
tts: false,
},
}

Expand Down
1 change: 1 addition & 0 deletions build-configs/malte/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const commonMalteBuildConfig: CommonBuildConfigType = {
fixedCity: null,
cityNotCooperatingTemplate: null,
chat: false,
tts: false,
},
aboutUrls: {
default: 'https://www.malteser-werke.de/malte-app',
Expand Down
1 change: 1 addition & 0 deletions build-configs/obdach/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const commonObdachBuildConfig: CommonBuildConfigType = {
fixedCity: null,
cityNotCooperatingTemplate: null,
chat: false,
tts: false,
},
aboutUrls: {
default: 'https://tuerantuer.de/digitalfabrik/projekte/netzwerkobdachwohnen/',
Expand Down
2 changes: 2 additions & 0 deletions native/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jest.mock('react-native-permissions', () => require('react-native-permissions/mo
// https://reactnavigation.org/docs/testing#mocking-native-modules
require('react-native-gesture-handler/jestSetup')

jest.mock('react-native-tts')

jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock')

Expand Down
2 changes: 2 additions & 0 deletions native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@
"react-native-safe-area-context": "^4.10.9",
"react-native-screens": "^3.34.0",
"react-native-svg": "^15.7.1",
"react-native-tts": "^4.1.1",
"react-native-url-polyfill": "^2.0.0",
"react-navigation-header-buttons": "^11.2.1",
"rrule": "^2.8.1",
"sentencex": "^0.4.2",
"shared": "0.0.1",
"styled-components": "^6.1.13",
"stylis": "^4.3.4",
Expand Down
1 change: 1 addition & 0 deletions native/src/@types/sentencex.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'sentencex'
23 changes: 13 additions & 10 deletions native/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import IOSSafeAreaView from './components/IOSSafeAreaView'
import SnackbarContainer from './components/SnackbarContainer'
import StaticServerProvider from './components/StaticServerProvider'
import StatusBar from './components/StatusBar'
import TtsContainer from './components/TtsContainer'
import { RoutesParamsType } from './constants/NavigationTypes'
import buildConfig from './constants/buildConfig'
import { userAgent } from './constants/endpoint'
Expand Down Expand Up @@ -87,16 +88,18 @@ const App = (): ReactElement => {
<SafeAreaProvider>
<AppContextProvider>
<SnackbarContainer>
<>
<StatusBar />
<IOSSafeAreaView>
<NavigationContainer onStateChange={onStateChange} linking={linking}>
<HeaderButtonsProvider stackType='native'>
<Navigator />
</HeaderButtonsProvider>
</NavigationContainer>
</IOSSafeAreaView>
</>
<TtsContainer>
<>
<StatusBar />
<IOSSafeAreaView>
<NavigationContainer onStateChange={onStateChange} linking={linking}>
<HeaderButtonsProvider stackType='native'>
<Navigator />
</HeaderButtonsProvider>
</NavigationContainer>
</IOSSafeAreaView>
</>
</TtsContainer>
</SnackbarContainer>
</AppContextProvider>
</SafeAreaProvider>
Expand Down
6 changes: 6 additions & 0 deletions native/src/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import MenuIcon from '../../../assets/icons/menu.svg'
import NewsIcon from '../../../assets/icons/news.svg'
import NoInternetIcon from '../../../assets/icons/no-internet.svg'
import NoteIcon from '../../../assets/icons/note.svg'
import PauseIcon from '../../../assets/icons/pause.svg'
import PhoneIcon from '../../../assets/icons/phone.svg'
import PlayIcon from '../../../assets/icons/play.svg'
import PlaybackIcon from '../../../assets/icons/playback.svg'
import POIsIcon from '../../../assets/icons/pois.svg'
import RefreshIcon from '../../../assets/icons/refresh.svg'
import SadSmileyIcon from '../../../assets/icons/sad-smiley.svg'
Expand Down Expand Up @@ -93,4 +96,7 @@ export {
TuNewsInactiveIcon,
WarningIcon,
WebsiteIcon,
PauseIcon,
PlaybackIcon,
PlayIcon,
}
2 changes: 2 additions & 0 deletions native/src/components/Categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { View } from 'react-native'
import { CATEGORIES_ROUTE, getCategoryTiles, RouteInformationType } from 'shared'
import { CategoriesMapModel, CategoryModel, CityModel } from 'shared/api'

import useTtsPlayer from '../hooks/useTtsPlayer'
import testID from '../testing/testID'
import { LanguageResourceCacheStateType } from '../utils/DataContainer'
import CategoryListItem from './CategoryListItem'
Expand Down Expand Up @@ -34,6 +35,7 @@ const Categories = ({
}: CategoriesProps): ReactElement => {
const children = categories.getChildren(category)
const cityCode = cityModel.code
useTtsPlayer(categories.isLeaf(category) ? category : undefined)

const navigateToCategory = ({ path }: { path: string }) =>
navigateTo({
Expand Down
15 changes: 15 additions & 0 deletions native/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import buildConfig from '../constants/buildConfig'
import dimensions from '../constants/dimensions'
import { AppContext } from '../contexts/AppContextProvider'
import useSnackbar from '../hooks/useSnackbar'
import useTtsPlayer from '../hooks/useTtsPlayer'
import createNavigateToFeedbackModal from '../navigation/createNavigateToFeedbackModal'
import navigateToLanguageChange from '../navigation/navigateToLanguageChange'
import sendTrackingSignal from '../utils/sendTrackingSignal'
Expand All @@ -51,6 +52,7 @@ enum HeaderButtonTitle {
Language = 'changeLanguage',
Location = 'changeLocation',
Search = 'search',
ReadAloud = 'readAloud',
Share = 'share',
Settings = 'settings',
Feedback = 'feedback',
Expand Down Expand Up @@ -83,6 +85,7 @@ const Header = ({
// Save route/canGoBack to state to prevent it from changing during navigating which would lead to flickering of the title and back button
const [previousRoute] = useState(navigation.getState().routes[navigation.getState().routes.length - 2])
const [canGoBack] = useState(navigation.canGoBack())
const { enabled: isTtsEnabled, setVisible: setTtsPlayerVisible, canRead } = useTtsPlayer()

const onShare = async () => {
if (!shareUrl) {
Expand Down Expand Up @@ -186,13 +189,25 @@ const Header = ({
renderItem(HeaderButtonTitle.Language, 'language', showItems, goToLanguageChange),
]

const openTtsPlayer = isTtsEnabled
? [
renderOverflowItem(t(HeaderButtonTitle.ReadAloud), () => {
setTtsPlayerVisible(canRead)
if (!canRead) {
showSnackbar({ text: t('nothingToReadFullMessage') })
}
}),
]
: []

const overflowItems = showOverflowItems
? [
...(shareUrl ? [renderOverflowItem(HeaderButtonTitle.Share, onShare)] : []),
...(!buildConfig().featureFlags.fixedCity
? [renderOverflowItem(HeaderButtonTitle.Location, () => navigation.navigate(LANDING_ROUTE))]
: []),
renderOverflowItem(HeaderButtonTitle.Settings, () => navigation.navigate(SETTINGS_ROUTE)),
...openTtsPlayer,
...(route.name !== NEWS_ROUTE ? [renderOverflowItem(HeaderButtonTitle.Feedback, navigateToFeedback)] : []),
...(route.name !== DISCLAIMER_ROUTE
? [renderOverflowItem(HeaderButtonTitle.Disclaimer, () => navigation.navigate(DISCLAIMER_ROUTE))]
Expand Down
2 changes: 2 additions & 0 deletions native/src/components/News.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { NavigationProps } from '../constants/NavigationTypes'
import { contentAlignment } from '../constants/contentDirection'
import useNavigate from '../hooks/useNavigate'
import useSetRouteTitle from '../hooks/useSetRouteTitle'
import useTtsPlayer from '../hooks/useTtsPlayer'
import Failure from './Failure'
import List from './List'
import LoadingSpinner from './LoadingSpinner'
Expand Down Expand Up @@ -62,6 +63,7 @@ const News = ({
}: NewsProps): ReactElement => {
const selectedNewsItem = news.find(_newsItem => _newsItem.id === newsId)
const { t } = useTranslation('news')
useTtsPlayer(selectedNewsItem)

const navigation = useNavigate().navigation as NavigationProps<NewsRouteType>
useSetRouteTitle({ navigation, title: getPageTitle(selectedNewsType, selectedNewsItem, t) })
Expand Down
6 changes: 6 additions & 0 deletions native/src/components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dimensions from '../constants/dimensions'
import useCityAppContext from '../hooks/useCityAppContext'
import useNavigateToLink from '../hooks/useNavigateToLink'
import useResourceCache from '../hooks/useResourceCache'
import useTtsPlayer from '../hooks/useTtsPlayer'
import { LanguageResourceCacheStateType, PageResourceCacheEntryStateType } from '../utils/DataContainer'
import { RESOURCE_CACHE_DIR_PATH } from '../utils/DatabaseConnector'
import Caption from './Caption'
Expand All @@ -17,6 +18,9 @@ import TimeStamp from './TimeStamp'
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;
`
export type ParsedCacheDictionaryType = Record<string, string>

const createCacheDictionary = (
Expand Down Expand Up @@ -60,6 +64,7 @@ const Page = ({
const resourceCacheUrl = useContext(StaticServerContext)
const [loading, setLoading] = useState(true)
const navigateToLink = useNavigateToLink()
const { visible: ttsPlayerVisible } = useTtsPlayer()

const cacheDictionary = useMemo(
() => createCacheDictionary(resourceCache, resourceCacheUrl, path),
Expand Down Expand Up @@ -90,6 +95,7 @@ const Page = ({
{!loading && AfterContent}
{!loading && !!content && lastUpdate && <TimeStamp lastUpdate={lastUpdate} />}
{!loading && Footer}
<SpaceForTts $ttsPlayerVisible={ttsPlayerVisible} />
</Container>
)
}
Expand Down
Loading

0 comments on commit 08e3ab8

Please sign in to comment.