跳到主要内容React Native 集成开源鸿蒙应用:WebView 桥接与原生模块方案 | 极客日志TypeScriptRNReact Native大前端
React Native 集成开源鸿蒙应用:WebView 桥接与原生模块方案
在 React Native 项目中集成开源鸿蒙(OpenHarmony)应用的两种主要方式:使用 WebView 加载鸿蒙应用网页版,以及通过 Native Modules 桥接本地代码。文章提供了具体的代码示例,包括 WebView 组件的引入与配置,以及 Native Module 的开发流程。此外,还展示了基于 React Native 的职业规划助手组件代码,并分析了该组件架构思想迁移至鸿蒙 ArkTS 框架的可行性,涉及 ArkUI 状态管理与方舟编译器的优化机制。最后说明了打包命令及运行效果。
深海蔚蓝20 浏览 在 React Native 中开发鸿蒙组件(这里指的是鸿蒙(HarmonyOS)组件),你需要了解鸿蒙开发的基础以及如何在 React Native 项目中集成鸿蒙应用。鸿蒙 OS 是由华为开发的一个分布式操作系统,主要用于其智能设备,如手机、平板、智能手表等。
1. 了解鸿蒙开发基础
首先,你需要熟悉鸿蒙 OS 的开发环境设置和基本开发流程。这包括:
- 开发工具:使用 DevEco Studio 作为开发 IDE。
- SDK:下载并安装 HarmonyOS SDK。
- 语言与框架:主要使用 Java/Kotlin 进行应用开发,但也可以通过 C/C++ 扩展功能。
2. 在 React Native 中集成鸿蒙应用
React Native 本身主要用于跨平台移动应用的开发,但你可以通过以下几种方式将鸿蒙应用集成到 React Native 项目中:
A. 使用 WebView
一种简单的方法是使用 WebView 来加载鸿蒙应用的网页版或通过一个 WebView 桥接本地代码与鸿蒙应用。
使用 WebView 加载鸿蒙应用的 URL:
import React from 'react';
import { WebView } from 'react-native-webview';
const HarmonyApp = () => {
return (
<WebView source={{ uri: 'https://your-harmony-app-url.com' }} style={{ flex: 1 }} />
);
};
export default HarmonyApp;
在 React Native 中添加 WebView:
npm install react-native-webview
B. 使用 Native Modules
创建一个 Native Module 来桥接 React Native 和鸿蒙原生应用。
- 在 DevEco Studio 中创建一个鸿蒙应用。
- 开发 Native Module:创建一个 Java/Kotlin 模块,在其中实现与鸿蒙应用交互的逻辑。
在 React Native 中调用 Native Module:使用 react-native-bridge 或其他桥接库来调用鸿蒙原生模块。例如,使用 react-native-bridge:
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
npm install react-native-bridge
import { NativeModules } from 'react-native';
const { HarmonyModule } = NativeModules;
C. 使用 Deep Linking 或 Intent 传递数据
如果你的鸿蒙应用支持 Deep Linking 或 Intent 传递数据,你可以在 React Native 中处理这些链接或 Intent,并据此与鸿蒙应用交互。
3. 真实演示案例代码
以下代码实现了一个职业规划助手的 React Native 组件,采用函数式组件和 Hooks 管理状态。通过 SafeAreaView 确保内容在安全区域内显示,使用 ScrollView 提供滚动支持。组件包含主页和技能发展两个主要功能模块,通过 TouchableOpacity 实现用户交互点击事件。职业发展阶段和技能发展数据通过预定义的常量数组进行管理,图标渲染通过预定义的 ICONS 对象映射字符实现,模态框用于显示详细信息。整体采用分层架构设计,将 UI 渲染、状态管理和业务逻辑分离。
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal } from 'react-native';
const ICONS = {
home: '🏠',
career: '💼',
skill: '🔧',
goal: '🎯',
info: 'ℹ',
close: '✕',
growth: '📈',
network: '🌐',
education: '🎓',
experience: '📅',
achievement: '🏆',
plan: '📝'
};
const CAREER_STAGES = [
{ id: 1, name: '入门阶段', duration: '0-2 年', description: '学习基础技能,积累经验' },
{ id: 2, name: '成长阶段', duration: '2-5 年', description: '提升专业能力,承担更多责任' },
{ id: 3, name: '成熟阶段', duration: '5-10 年', description: '成为领域专家,指导他人' },
{ id: 4, name: '领导阶段', duration: '10 年以上', description: '制定战略,引领团队发展' }
];
const SKILL_DEVELOPMENT = [
{ id: 1, category: '技术技能', skills: ['编程', '数据分析', '系统设计'], level: '初级' },
{ id: 2, category: '软技能', skills: ['沟通', '团队合作', '领导力'], level: '中级' },
{ id: 3, category: '行业知识', skills: ['市场趋势', '法规政策', '最佳实践'], level: '初级' }
];
const CareerPlanning: React.FC = () => {
const [selectedStage, setSelectedStage] = useState<any>(null);
const [selectedSkill, setSelectedSkill] = useState<any>(null);
const [modalVisible, setModalVisible] = useState(false);
const [infoModalVisible, setInfoModalVisible] = useState(false);
const [activeTab, setActiveTab] = useState('home');
const renderIcon = (iconName: string, style: any) => {
return (
<Text style={[styles.iconText, style]}>{ICONS[iconName as keyof typeof ICONS] || '□'}</Text>
);
};
const showStageDetails = (stage: any) => {
setSelectedStage(stage);
setModalVisible(true);
};
const showSkillDetails = (skill: any) => {
setSelectedSkill(skill);
setModalVisible(true);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>💼 职业发展规划</Text>
<Text style={styles.subtitle}>规划您的职业发展路径</Text>
<TouchableOpacity style={styles.infoButton} onPress={() => setInfoModalVisible(true)}>
{renderIcon('info', styles.infoIcon)}
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={styles.content}>
{activeTab === 'home' && (
<View>
<View style={styles.welcomeCard}>
<Text style={styles.welcomeTitle}>欢迎来到职业规划中心</Text>
<Text style={styles.welcomeText}>制定清晰的职业目标,实现职业梦想</Text>
</View>
< =>
职业发展阶段
了解不同阶段的成长路径
{CAREER_STAGES.map((stage) => (
showStageDetails(stage)}>
{renderIcon('growth', styles.stageIconText)}
{stage.name}
{stage.duration}
{stage.description}
showStageDetails(stage)}>
详情
))}
)}
{activeTab === 'skill' && (
技能发展
技术技能
初级
• 编程基础
• 数据分析
• 系统设计
软技能
中级
• 沟通能力
• 团队合作
• 领导力
行业知识
初级
• 市场趋势
• 法规政策
• 最佳实践
)}
{activeTab === 'goal' && (
职业目标
{renderIcon('achievement', {})}
短期目标 (1 年)
获得专业认证,提升技术能力
{renderIcon('growth', {})}
中期目标 (3 年)
晋升为高级工程师,领导项目
{renderIcon('career', {})}
长期目标 (5 年)
成为技术专家或团队负责人
)}
setActiveTab('home')}>
{renderIcon('home', styles.tabIcon)}
首页
setActiveTab('skill')}>
{renderIcon('skill', styles.tabIcon)}
技能
setActiveTab('goal')}>
{renderIcon('goal', styles.tabIcon)}
目标
{/* 详情模态框 */}
setModalVisible(false)}>
{selectedStage ? selectedStage.name : selectedSkill ? selectedSkill.category : '详情'}
setModalVisible(false)}>
{renderIcon('close', {})}
{selectedStage && (
阶段:
{selectedStage.name}
时长:
{selectedStage.duration}
描述:
{selectedStage.description}
关键任务:
{selectedStage.name.includes('入门') && '学习基础知识,完成分配任务,建立职业基础'}{selectedStage.name.includes('成长') && '承担更多责任,提升专业技能,参与项目决策'}{selectedStage.name.includes('成熟') && '成为领域专家,指导新人,优化工作流程'}{selectedStage.name.includes('领导') && '制定战略方向,管理团队,推动业务发展'}
制定计划
设为当前
)}
{selectedSkill && (
技能类别:
{selectedSkill.category}
技能列表:
{selectedSkill.skills.join(', ')}
当前水平:
{selectedSkill.level}
发展建议:
通过培训、实践和项目经验提升技能水平
学习计划
评估技能
)}
{/* 应用说明模态框 */}
setInfoModalVisible(false)}>
职业发展规划助手
setInfoModalVisible(false)}>
{renderIcon('close', {})}
功能介绍
• 职业阶段规划{'\n'} • 技能发展建议{'\n'} • 目标设定与追踪{'\n'} • 职业路径推荐
职业发展建议
• 持续学习新技术{'\n'} • 建立专业网络{'\n'} • 设定可衡量目标{'\n'} • 定期评估进展
成功要素
• 明确的职业目标{'\n'} • 持续的技能提升{'\n'} • 良好的人际关系{'\n'} • 积极的工作态度
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#f0f7ff' },
header: { paddingTop: 30, paddingBottom: 20, paddingHorizontal: 20, backgroundColor: '#ffffff', borderBottomWidth: 1, borderBottomColor: '#c7d2fe', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
title: { fontSize: 22, fontWeight: 'bold', color: '#1e3a8a' },
subtitle: { fontSize: 13, color: '#3730a3', marginTop: 4 },
infoButton: { width: 36, height: 36, borderRadius: 18, backgroundColor: '#c7d2fe', alignItems: 'center', justifyContent: 'center' },
infoIcon: { fontSize: 20, color: '#3730a3' },
iconText: { fontSize: 20 },
content: { padding: 16, paddingBottom: 80 },
welcomeCard: { backgroundColor: '#e0e7ff', borderRadius: 16, padding: 20, marginBottom: 20, elevation: 4, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8 },
welcomeTitle: { fontSize: 18, fontWeight: 'bold', color: '#1e3a8a', marginBottom: 8 },
welcomeText: { fontSize: 14, color: '#3730a3' },
sectionTitleContainer: { marginBottom: 15 },
sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#1e3a8a', marginBottom: 5 },
sectionSubtitle: { fontSize: 14, color: '#3730a3' },
stageList: {},
stageCard: { backgroundColor: '#ffffff', borderRadius: 12, padding: 15, marginBottom: 12, flexDirection: 'row', alignItems: 'center', elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 4 },
stageIcon: { width: 50, height: 50, borderRadius: 25, backgroundColor: '#e0e7ff', alignItems: 'center', justifyContent: 'center', marginRight: 15 },
stageIconText: { fontSize: 24 },
stageInfo: { flex: 1 },
stageName: { fontSize: 16, fontWeight: 'bold', color: '#1e3a8a' },
stageDuration: { fontSize: 14, color: '#3730a3', marginTop: 4 },
stageDescription: { fontSize: 14, color: '#4f46e5', marginTop: 4 },
stageButton: { backgroundColor: '#6366f1', paddingHorizontal: 15, paddingVertical: 8, borderRadius: 8 },
stageButtonText: { color: '#ffffff', fontWeight: 'bold' },
tabContent: {},
tabTitle: { fontSize: 20, fontWeight: 'bold', color: '#1e3a8a', marginBottom: 15 },
skillCard: { backgroundColor: '#ffffff', borderRadius: 12, padding: 15, marginBottom: 12, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 4 },
skillHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 },
skillCategory: { fontSize: 16, fontWeight: 'bold', color: '#1e3a8a' },
skillLevel: { fontSize: 14, color: '#6366f1', fontWeight: '600' },
skillList: {},
skillItem: { fontSize: 14, color: '#3730a3', marginBottom: 5 },
goalCard: { backgroundColor: '#ffffff', borderRadius: 12, padding: 15, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 4 },
goalItem: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 15 },
goalIcon: { fontSize: 24, marginRight: 10, marginTop: 2 },
goalInfo: { flex: 1 },
goalTitle: { fontSize: 16, fontWeight: 'bold', color: '#1e3a8a' },
goalDescription: { fontSize: 14, color: '#3730a3', marginTop: 4 },
tabBar: { flexDirection: 'row', backgroundColor: '#ffffff', borderTopWidth: 1, borderTopColor: '#c7d2fe', paddingVertical: 10, position: 'absolute', bottom: 0, left: 0, right: 0 },
tabButton: { flex: 1, alignItems: 'center', paddingVertical: 8 },
activeTab: { backgroundColor: '#e0e7ff', borderRadius: 10, marginHorizontal: 8 },
tabIcon: { fontSize: 20, marginBottom: 4 },
tabText: { fontSize: 12, color: '#3730a3' },
activeTabText: { color: '#1e3a8a', fontWeight: 'bold' },
modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center' },
modalContent: { backgroundColor: '#ffffff', width: '90%', height: '60%', borderRadius: 20, overflow: 'hidden' },
infoModalContent: { backgroundColor: '#ffffff', width: '90%', height: '50%', borderRadius: 20, overflow: 'hidden' },
modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 20, borderBottomWidth: 1, borderBottomColor: '#c7d2fe', backgroundColor: '#f0f7ff' },
modalTitle: { fontSize: 20, fontWeight: 'bold', color: '#1e3a8a' },
closeButton: { fontSize: 30, color: '#a5b4fc', fontWeight: '200' },
modalBody: { flex: 1, padding: 20 },
infoModalBody: { flex: 1, padding: 20 },
detailItem: { marginBottom: 15 },
detailLabel: { fontSize: 14, color: '#1e3a8a', fontWeight: '600', marginBottom: 4 },
detailValue: { fontSize: 14, color: '#3730a3', backgroundColor: '#f0f7ff', padding: 10, borderRadius: 6 },
actionButtons: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 },
actionButton: { backgroundColor: '#6366f1', paddingHorizontal: 15, paddingVertical: 10, borderRadius: 8, flex: 1, marginHorizontal: 5 },
actionButtonText: { color: '#ffffff', fontWeight: 'bold', textAlign: 'center' },
infoTitle: { fontSize: 20, fontWeight: 'bold', color: '#1e3a8a', marginBottom: 15, textAlign: 'center' },
infoText: { fontSize: 15, color: '#3730a3', lineHeight: 22, marginBottom: 15 },
infoSubtitle: { fontSize: 17, fontWeight: 'bold', color: '#1e3a8a', marginBottom: 10 }
});
export default CareerPlanning;
在鸿蒙系统环境下,该组件的核心架构思想可以无缝迁移至 ArkTS 开发框架。鸿蒙的 ArkUI 框架支持声明式 UI 编程范式,其状态管理机制 (@State, @Prop, @Link 等装饰器) 与 React Hooks 概念相似但功能更强大。当用户点击查看职业阶段详情时,selectedStage 状态更新会触发本地 UI 重渲染,还可通过 @Link 装饰器将状态变化同步到其他关联设备,实现跨设备职业规划信息实时共享。这种分布式状态同步依托于鸿蒙的分布式数据管理 (DDM) 和分布式软总线 (DSoftBus) 技术,在不同设备间建立高带宽、低延迟通信通道。
鸿蒙系统的方舟编译器 (ArkCompiler) 能够对这类组件进行深度优化。编译阶段,方舟编译器分析组件状态依赖关系和渲染树结构,生成高效的中间表示 (IR),运行时通过 JIT 和 AOT 结合优化 JavaScript/TypeScript 代码执行。对于频繁状态更新操作 (如切换 Tab 页),方舟编译器利用 PGO 技术根据实际运行时数据优化渲染路径,减少不必要的虚拟 DOM 计算和真实 DOM 操作。鸿蒙的多线程渲染架构将 UI 渲染任务分配到独立 UI 线程,与 JavaScript 执行线程解耦,确保复杂职业信息渲染场景下保持流畅用户体验。
4. 打包
接下来通过打包命令 npm run harmony 将 React Native 的代码打包成为 bundle,这样可以进行在开源鸿蒙 OpenHarmony 中进行使用。
打包之后再将打包后的鸿蒙 OpenHarmony 文件拷贝到鸿蒙的 DevEco-Studio 工程目录去:
View
style
{styles.sectionTitleContainer}
<Text style={styles.sectionTitle}>
</Text>
<Text style={styles.sectionSubtitle}>
</Text>
</View>
<View style={styles.stageList}>
<TouchableOpacity key={stage.id} style={styles.stageCard} onPress={() =>
<View style={styles.stageIcon}>
</View>
<View style={styles.stageInfo}>
<Text style={styles.stageName}>
</Text>
<Text style={styles.stageDuration}>
</Text>
<Text style={styles.stageDescription}>
</Text>
</View>
<TouchableOpacity style={styles.stageButton} onPress={() =>
<Text style={styles.stageButtonText}>
</Text>
</TouchableOpacity>
</TouchableOpacity>
</View>
</View>
<View style={styles.tabContent}>
<Text style={styles.tabTitle}>
</Text>
<View style={styles.skillCard}>
<View style={styles.skillHeader}>
<Text style={styles.skillCategory}>
</Text>
<Text style={styles.skillLevel}>
</Text>
</View>
<View style={styles.skillList}>
<Text style={styles.skillItem}>
</Text>
<Text style={styles.skillItem}>
</Text>
<Text style={styles.skillItem}>
</Text>
</View>
</View>
<View style={styles.skillCard}>
<View style={styles.skillHeader}>
<Text style={styles.skillCategory}>
</Text>
<Text style={styles.skillLevel}>
</Text>
</View>
<View style={styles.skillList}>
<Text style={styles.skillItem}>
</Text>
<Text style={styles.skillItem}>
</Text>
<Text style={styles.skillItem}>
</Text>
</View>
</View>
<View style={styles.skillCard}>
<View style={styles.skillHeader}>
<Text style={styles.skillCategory}>
</Text>
<Text style={styles.skillLevel}>
</Text>
</View>
<View style={styles.skillList}>
<Text style={styles.skillItem}>
</Text>
<Text style={styles.skillItem}>
</Text>
<Text style={styles.skillItem}>
</Text>
</View>
</View>
</View>
<View style={styles.tabContent}>
<Text style={styles.tabTitle}>
</Text>
<View style={styles.goalCard}>
<View style={styles.goalItem}>
<Text style={styles.goalIcon}>
</Text>
<View style={styles.goalInfo}>
<Text style={styles.goalTitle}>
</Text>
<Text style={styles.goalDescription}>
</Text>
</View>
</View>
<View style={styles.goalItem}>
<Text style={styles.goalIcon}>
</Text>
<View style={styles.goalInfo}>
<Text style={styles.goalTitle}>
</Text>
<Text style={styles.goalDescription}>
</Text>
</View>
</View>
<View style={styles.goalItem}>
<Text style={styles.goalIcon}>
</Text>
<View style={styles.goalInfo}>
<Text style={styles.goalTitle}>
</Text>
<Text style={styles.goalDescription}>
</Text>
</View>
</View>
</View>
</View>
</ScrollView>
<View style={styles.tabBar}>
<TouchableOpacity style={[styles.tabButton, activeTab === 'home' && styles.activeTab]} onPress={() =>
<Text style={[styles.tabText, activeTab === 'home' && styles.activeTabText]}>
</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.tabButton, activeTab === 'skill' && styles.activeTab]} onPress={() =>
<Text style={[styles.tabText, activeTab === 'skill' && styles.activeTabText]}>
</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.tabButton, activeTab === 'goal' && styles.activeTab]} onPress={() =>
<Text style={[styles.tabText, activeTab === 'goal' && styles.activeTabText]}>
</Text>
</TouchableOpacity>
</View>
<Modal animationType="slide" transparent={true} visible={modalVisible} onRequestClose={() =>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>
</Text>
<TouchableOpacity onPress={() =>
<Text style={styles.closeButton}>
</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.modalBody}>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.actionButtons}>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionButtonText}>
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionButtonText}>
</Text>
</TouchableOpacity>
</View>
</ScrollView>
<ScrollView style={styles.modalBody}>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.detailItem}>
<Text style={styles.detailLabel}>
</Text>
<Text style={styles.detailValue}>
</Text>
</View>
<View style={styles.actionButtons}>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionButtonText}>
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.actionButtonText}>
</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View>
</View>
</Modal>
<Modal animationType="slide" transparent={true} visible={infoModalVisible} onRequestClose={() =>
<View style={styles.modalOverlay}>
<View style={styles.infoModalContent}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>
</Text>
<TouchableOpacity onPress={() =>
<Text style={styles.closeButton}>
</Text>
</TouchableOpacity>
</View>
<ScrollView style={styles.infoModalBody}>
<Text style={styles.infoTitle}>
</Text>
<Text style={styles.infoText}>
</Text>
<Text style={styles.infoSubtitle}>
</Text>
<Text style={styles.infoText}>
</Text>
<Text style={styles.infoSubtitle}>
</Text>
<Text style={styles.infoText}>
</Text>
</ScrollView>
</View>
</View>
</Modal>
</SafeAreaView>