React Native Accessibility TalkBack не работает на внутренних экранах в Android 6 - PullRequest
0 голосов
/ 08 марта 2019

Я использую ReactNative с Expo, и я заметил, что в Android 6 функция доступности TalkBack не работает должным образом. На первом экране он работает, но на следующих экранах он не работает (который также динамически загружает FlatList). Я проверил на последней версии Android 7, 8, и там функция TalkBack работает довольно хорошо на всех экранах.

Вот мой пример кода:

Package.json

{
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "eject": "expo eject",
    "test": "node ./node_modules/jest/bin/jest.js --watchAll"
  },
  "jest": {
    "preset": "jest-expo"
  },
  "dependencies": {
      "@expo/samples": "2.1.1",
      "expo": "^32.0.0",
      "react": "16.5.0",
      "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
      "react-native-cheerio": "^1.0.0-rc.4",
      "react-native-xml2js": "^1.0.3",
      "react-navigation": "^3.0.9"
  },
  "devDependencies": {
      "babel-preset-expo": "^5.0.0",
      "jest-expo": "^32.0.0"
  },
  "private": true
}

HomeScreen

import React from 'react';
import {
    Image,
    Platform,
    ScrollView,
    StyleSheet,
    Text,
    TouchableOpacity,
    View, FlatList, ToastAndroid
  } from 'react-native';

import {NEWS_SOURCES} from '../libraries/loader';


export const Toast = (props) => {
    if (props.visible) {
        ToastAndroid.showWithGravityAndOffset(props.message, ToastAndroid.LONG, ToastAndroid.BOTTOM, 25, 50);
        return null;
    }
    return null;
};



export default class HomeScreen extends React.Component {

    static navigationOptions = {
        header: null,
    };

    constructor(props){
        super(props);
        this._renderItem = this._renderItem.bind(this);
        this._handleItemClick = this._handleItemClick.bind(this);
    }

    render() {
        console.log('\nnavigation props: ', this.props.navigation);
        return (
            <ScrollView style={styles.container} accessibilityLiveRegion="polite">

                <View style={{flex:1, flexDirection: 'column', marginTop:50}}>
                    <Text style={styles.app_name} accessibilityLabel={"Select 1 of "+NEWS_SOURCES.length+" News Channels, from the list - given on screen"}>News Assistant</Text>
                </View>
                <View style={{flex:5, flexDirection: 'column'}}>
                    <FlatList
                        style={{padding:8}}
                        data={NEWS_SOURCES}
                        renderItem={this._renderItem}
                        keyExtractor={this._keyExtractor}
                    />
                </View>
                <Toast visible={true} message={'Welcome, to news assistant. We got '+NEWS_SOURCES.length+' news channels loaded for you. Tap to know their names. And double tap to read headlines, on next screen.'} />
            </ScrollView>
        );
    }

    _handleItemClick(source_index) {
        var selectedSource = NEWS_SOURCES[source_index];
        console.log('Channel Selected: '+source_index, selectedSource);
        this.props.navigation.navigate('HeadlinesStack', {channel_data: NEWS_SOURCES[source_index]});
    }

    _renderItem(obj){
        console.log(obj);
        return (
            <View key={obj.index} style={styles.row} >
                <TouchableOpacity style={{}} onPress={()=>this._handleItemClick(obj.index)} accessible={true} accessibilityRole="button" accessibilityLabel={obj.item.id+'. '+obj.item.title}>
                  <Text style={[styles.lightTxt, styles.rowTxt]}>{obj.item.title}</Text>
                </TouchableOpacity>
            </View>
      );
   }

   _keyExtractor = (item, index) => 'key'+item.id;

}




const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#000',
    },
    app_name:{
        color: '#fff', textAlign: 'center', fontSize:18
    },
    lightTxt: {
        color: '#fff'
    },
    rowTxt:{
        padding:25
    },
    row: {
        borderBottomWidth: 1, borderBottomColor: '#333', borderStyle: 'solid'
   },

});

HeadlinesScreen.js

import React from 'react';
import { ScrollView, View, Text, FlatList, TouchableOpacity, Modal, Alert, StyleSheet, ActivityIndicator, Button, ToastAndroid, TouchableHighlight, UIManager, findNodeHandle } from 'react-native';
import {NEWS_SOURCES, fetch_newsByCountry, fetch_newsBySourceID, fetch_newsFullStory} from '../libraries/loader';


export const Toast=(props)=> {
    if (props.visible) {
        ToastAndroid.showWithGravityAndOffset(props.message, ToastAndroid.LONG, ToastAndroid.BOTTOM, 25, 50);
        return null;
    }
    return null;
};

export default class HeadlinesScreen extends React.Component {
    static navigationOptions= {
        title: 'Headlines',
    };

constructor(props){
          super(props);
          this.state = { channelSource: this.props.navigation.getParam('channel_data', 'none'),   // default value 'none' 
              dataList: [], modalVisible: false, selectedItemId: null, selectedItem_desc: null };
          this._renderItem = this._renderItem.bind(this);
          this._handleItemClick = this._handleItemClick.bind(this);
          this._displayItemDetails = this._displayItemDetails.bind(this); 
          this._setModalVisible = this._setModalVisible.bind(this);   
          this._onFetchComplete  = this._onFetchComplete.bind(this);
          this._forceTriggerSpeak = this._forceTriggerSpeak.bind(this);
      }

      render() {
            console.log('\n\this.props.navigation:', this.props.navigation);
            console.log('\n\nSelected Channel state.params Data: ', this.props.navigation.state.params);
            console.log('\n\nSelected Channel param Data: ', this.props.navigation.getParam('channel_data'));
            console.log('\n\nSelected Channel state Data: ', this.state.channelSource);
            //return (<View></View>) ;
            //console.log('\n\nRender: \n',this.state);
            console.log('\n\nRender -  modal state: \n',this.state.modalVisible);
            return (
                <ScrollView style={styles.container} accessibilityLiveRegion="polite" importantForAccessibility={this.state.modalVisible==false ? 'yes' : 'no-hide-descendants'}>
                    {(this.state.dataList.length ==0? <ActivityIndicator size="large" Accessible={true} accessibilityLabel="Loading news, please wait." /> : 
                        this.state.dataList.map(this._renderItem)
                    )} 
                  <Toast visible={this.state.dataList.length ==0 && this.state.modalVisible==false ? true: false} message="Loading news, please wait." />
                  <Toast visible={this.state.dataList.length > 0 && this.state.modalVisible==false ? true: false} message={this.state.dataList.length+' news loaded. Tap to read headline. And double tap to read details.'} />
                  <Toast visible={this.state.dataList.length > 0 && this.state.modalVisible==true ? true: false} message={'Loading full story, please wait.'} />
                  <Toast visible={this.state.dataList.length > 0 && this.state.modalVisible==true && this.state.selectedItem_desc != null? true: false} message={'Story loaded, please tap on screen to read. And scroll bottom of screen and hit Close button, to close this story.'} />

                  {(this.state.modalVisible ? this._displayItemDetails() : null)}

                </ScrollView>
              );
    }


    async componentDidMount(){
        console.log("\n\n\n", this.props.navigation.getParam('channel_data'));
        var newsList = await fetch_newsBySourceID(this.state.channelSource, this._onFetchComplete);       // external api call
    }

   _onFetchComplete(payload, payloadType){   // payloadType: list | story
        console.log('\n\n onFetchComplete list: \n', payload); 

        if(Array.isArray(payload) && payloadType == 'list'){
            console.log('\n\n onFetchComplete list: \n'+payload.length); 
            this.setState(previousState => {
                return { dataList: payload};
            });
        }
        else{
            this.setState(previousState => {
                return { selectedItem_desc: payload};
            });
        } 
    }


    _renderItem(obj, index){
        return (
            <View key={index} style={{ borderBottomWidth: 1, borderBottomColor: '#ccc', borderStyle: 'solid', padding:10}} >
                <TouchableOpacity style={{flexDirection: 'row', }} onPress={()=>this._handleItemClick(index)} accessible={true} accessibilityLiveRegion="polite" accessibilityLabel={(index-1+2)+". "+obj.title} accessibilityHint="Double tap to read full news on next screen.">
                   <Text style={[styles.lightTxt, styles.rowTxt]} >{obj.title}</Text>
                </TouchableOpacity>
            </View>
        );
    }


    async _handleItemClick(itemIndex){
        console.log('showModal for item: '+itemIndex);
        fetch_newsFullStory(this.state.dataList[itemIndex].story_url, this.state.channelSource, this._onFetchComplete);
    }

    _forceTriggerSpeak(){
        UIManager.sendAccessibilityEvent( findNodeHandle(this), UIManager.AccessibilityEventTypes.typeViewClicked);  
    }

            // usually item details in a Popup Modal View
    _displayItemDetails(){  // this.state.dataList[this.state.selectedItemId].content
        console.log('\n\nDisplay Modal:\n', this.state.modalVisible);
    }

          // usually used to hide Popup Modal
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        paddingTop: 15,
        backgroundColor: '#000',
    },
    lightTxt: {
        color: '#fff'
    },
    rowTxt:{
        padding:25
    },
});
...