在这里插入图片描述

在 React Native 鸿蒙跨端开发中,组件复用性、交互流畅度和多端适配性是核心诉求。本文将以一份完整的通知中心代码为例,从技术架构、核心组件设计、跨端适配技巧三个维度,深度解析如何在 React Native 中构建兼容鸿蒙系统的高质量交互组件,尤其聚焦气泡弹出框(Popover)和瀑布流布局两大核心场景。

1.1 技术栈

import React, { useState, useRef, useEffect } from 'react';
import { 
  View, Text, StyleSheet, ScrollView, SafeAreaView,
  Image, Dimensions, TouchableOpacity, Animated,
  FlatList, Modal
} from 'react-native';
技术选型
  • 核心框架:React + React Native,利用 React 的声明式编程模型和组件化思想,结合 React Native 的原生渲染能力,实现 “一次编写,多端运行”
  • 关键 API 依赖
    • Animated:用于气泡弹出框的缩放/淡入淡出动画,保证鸿蒙系统下的流畅交互
    • FlatList:实现瀑布流布局,相比 ScrollView 具备懒加载特性,优化鸿蒙设备的内存占用
    • Modal:作为气泡弹出框的底层容器,提供跨端一致的遮罩层实现
    • Dimensions:适配鸿蒙设备的屏幕尺寸,实现响应式布局
  • 鸿蒙适配核心:基于 React Native 的跨端特性,通过 StyleSheet 样式隔离、原生组件 API 封装,确保组件在鸿蒙系统(HarmonyOS)上的兼容性,无需额外编写鸿蒙原生代码

1.2 项目结构

App.tsx
├── 常量定义(POPOVER_ICONS):Base64 图标库,减少网络请求
├── 类型接口(Interface):PopoverOption、NoticeItem 等,强类型约束
├── 核心组件:
│   ├── Popover:气泡弹出框组件(支持动画、位置校准)
│   ├── LazyImage:懒加载图片组件(适配鸿蒙网络环境)
│   ├── WaterfallNoticeItem:瀑布流通知项(长按触发 Popover)
│   └── App:主应用组件(状态管理、数据渲染)
└── 工具函数:getTypeColor、getTypeIcon,统一样式逻辑
架构设计
  • 组件职责单一:每个组件聚焦特定功能,便于鸿蒙跨端场景下的维护和复用
  • 强类型约束:通过 TypeScript 接口定义数据结构,减少跨端开发中的类型错误
  • Base64 图标方案:避免鸿蒙系统下的图标资源路径问题,提升组件加载速度

2.1 气泡弹出框(Popover)组件:

气泡弹出框是跨端应用中高频交互组件,需解决鸿蒙系统下的动画流畅度、位置校准、遮罩层交互三大核心问题。

2.1.1 组件接口
interface PopoverOption {
  id: string;
  label: string;
  icon?: string;
  action: () => void;
}

interface PopoverProps {
  visible: boolean;
  options: PopoverOption[];
  onClose: () => void;
  anchorPosition: { x: number; y: number };
}
  • 接口设计思路:通过 anchorPosition 实现弹出框与触发元素的位置绑定,options 支持自定义操作项,满足跨端场景下的灵活配置需求
  • 鸿蒙适配考量:接口设计与 React Native 原生组件保持一致,避免鸿蒙系统下的 API 兼容问题
2.1.2 动画效果
const scaleAnim = useRef(new Animated.Value(0)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;

useEffect(() => {
  if (visible) {
    Animated.parallel([
      Animated.spring(scaleAnim, {
        toValue: 1,
        tension: 300,
        friction: 20,
        useNativeDriver: true
      }),
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 200,
        useNativeDriver: true
      })
    ]).start();
  } else {
    Animated.parallel([
      Animated.timing(scaleAnim, {
        toValue: 0,
        duration: 150,
        useNativeDriver: true
      }),
      Animated.timing(fadeAnim, {
        toValue: 0,
        duration: 150,
        useNativeDriver: true
      })
    ]).start();
  }
}, [visible]);
动画实现
  • 并行动画:使用 Animated.parallel 同时执行缩放和淡入淡出动画,提升鸿蒙系统下的交互体验
  • Spring 动画:通过 tension(张力)和 friction(摩擦力)参数,模拟物理弹性效果,比线性动画更自然
  • useNativeDriver: true:关键优化!让动画在原生线程执行,避免鸿蒙系统下的 JS 线程阻塞,保证 60fps 流畅度
2.1.3 遮罩层设计
<Modal transparent={true} animationType="none" visible={visible} onRequestClose={onClose}>
  <TouchableOpacity 
    style={styles.popoverBackdrop} 
    activeOpacity={1} 
    onPress={onClose}
  >
    <Animated.View 
      style={[
        styles.popoverContainer,
        {
          top: anchorPosition.y,
          left: anchorPosition.x,
          transform: [{ scale: scaleAnim }],
          opacity: fadeAnim
        }
      ]}
    >
      <View style={styles.popoverTriangle} />
      <View style={styles.popoverContent}>
        {/* 操作项列表 */}
      </View>
    </Animated.View>
  </TouchableOpacity>
</Modal>
适配技巧
  • 透明 Modal 遮罩transparent={true} 实现半透明遮罩,animationType="none" 避免鸿蒙系统下的默认动画冲突
  • 位置校准:通过 anchorPosition 动态计算弹出框位置,在瀑布流组件中通过 pageX/pageY 获取触发元素坐标,解决鸿蒙设备不同屏幕尺寸下的位置偏移问题
  • 三角形指示符:使用 CSS 边框实现弹出框指向箭头,通过 transform: [{ rotate: '180deg' }] 适配不同触发位置,避免鸿蒙系统下的 SVG 兼容性问题

2.2 懒加载图片(LazyImage)组件:

在鸿蒙跨端应用中,图片加载是性能瓶颈之一,尤其在弱网络环境下,需实现加载中、加载失败、成功加载的完整状态管理。

核心实现代码
const LazyImage = ({ source, style }: { source: { uri: string }; style: any }) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  return (
    <View style={style}>
      {loading && !error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: '#334155', justifyContent: 'center', alignItems: 'center' }]}>
          <Image source={{ uri: POPOVER_ICONS.notification }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
        </View>
      )}
      {error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: '#dc2626', justifyContent: 'center', alignItems: 'center' }]}>
          <Image source={{ uri: POPOVER_ICONS.close }} style={{ width: 30, height: 30, tintColor: '#ffffff' }} />
        </View>
      )}
      <Image
        source={source}
        style={StyleSheet.absoluteFill}
        onLoad={() => setLoading(false)}
        onError={() => {
          setLoading(false);
          setError(true);
        }}
        resizeMode="cover"
      />
    </View>
  );
};
技术亮点
  • 状态管理:通过 loadingerror 状态,覆盖图片加载全生命周期,提升鸿蒙用户体验
  • 占位图优化:使用 Base64 图标作为占位图,避免鸿蒙系统下的图片资源加载失败问题
  • 性能优化StyleSheet.absoluteFill 实现图片与占位图的绝对定位,避免布局抖动;resizeMode="cover" 适配鸿蒙设备的不同屏幕比例
    在这里插入图片描述

2.3 瀑布流通知列表:

瀑布流布局是通知中心的核心UI,需解决鸿蒙系统下的多列布局、数据渲染、长按交互三大问题。

2.3.1 瀑布流
<FlatList
  data={waterfallNotices}
  renderItem={({ item }) => <WaterfallNoticeItem notice={item} onLongPress={() => {}} />}
  keyExtractor={item => item.id}
  numColumns={2}
  contentContainerStyle={styles.waterfallListContainer}
  showsVerticalScrollIndicator={false}
/>
关键技术点
  • numColumns={2}:实现两列瀑布流,React Native 的 FlatList 原生支持多列布局,无需额外第三方库,鸿蒙系统兼容性更好
  • keyExtractor:指定唯一 key,优化鸿蒙系统下的列表渲染性能,避免重复渲染
  • contentContainerStyle:统一列表内边距,适配鸿蒙设备的屏幕尺寸
2.3.2 通知项组件(WaterfallNoticeItem)
const handleLongPress = (event: any) => {
  const { pageX, pageY } = event.nativeEvent;
  setAnchorPosition({ x: pageX - 150, y: pageY - 200 }); // 位置校准
  setShowPopover(true);
};
  • 长按触发逻辑:通过 onLongPress 事件获取触摸点坐标,计算 Popover 弹出位置,解决鸿蒙系统下的触摸事件兼容性问题
  • 跨端交互一致性:长按触发气泡弹出框的交互逻辑,与 iOS/Android 保持一致,降低鸿蒙用户的学习成本

2.4 样式设计:

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
  waterfallNoticeItem: {
    backgroundColor: '#1e293b',
    margin: 5,
    borderRadius: 12,
    overflow: 'hidden',
    borderWidth: 1,
    borderColor: '#334155',
    position: 'relative',
    flex: 1, // 关键:实现两列等宽布局
  },
  // 其他样式...
});
样式适配
  • Flex 布局优先:使用 flex: 1 实现两列等宽布局,适配鸿蒙设备的不同屏幕宽度
  • 颜色方案:采用深色主题(#0f172a 背景),符合鸿蒙系统的设计规范,同时减少屏幕功耗
  • 边框与圆角:统一使用 borderWidth: 1borderRadius: 12,避免鸿蒙系统下的样式渲染差异
  • Dimensions 动态适配:通过 Dimensions.get('window') 获取屏幕尺寸,实现响应式布局,适配鸿蒙手机、平板等不同设备

3.1 组件复用

  • Base64 图标替代本地资源:避免鸿蒙系统下的资源路径问题,同时减少网络请求,提升加载速度
  • 原生组件 API 封装:对 React Native 原生组件(如 Modal、Animated)进行二次封装,屏蔽鸿蒙系统的 API 差异
  • 样式隔离:使用 StyleSheet.create 定义样式,避免 inline 样式在鸿蒙系统下的渲染性能问题

3.2 性能

  • FlatList 懒加载:相比 ScrollView,FlatList 支持item 懒加载,减少鸿蒙设备的内存占用,尤其适合长列表场景
  • useNativeDriver 动画优化:所有动画均设置 useNativeDriver: true,避免 JS 线程阻塞,保证鸿蒙系统下的动画流畅度
  • 图片懒加载:通过 LazyImage 组件实现图片按需加载,减少鸿蒙弱网络环境下的流量消耗和加载延迟

3.3 交互

  • 触摸事件统一:使用 React Native 标准的 onLongPressonPress 事件,避免鸿蒙系统下的触摸事件兼容性问题
  • 动画效果统一:采用一致的动画参数(如 duration: 200ms),保证在鸿蒙、iOS、Android 上的交互体验一致
  • 弹窗层级管理:通过 zIndex: 1000 确保 Popover 组件在鸿蒙系统下的层级最高,避免被其他组件遮挡

本文通过一份完整的通知中心代码,深度解析了 React Native 鸿蒙跨端开发中气泡弹出框、瀑布流布局、图片懒加载三大核心场景的实现方案。关键亮点包括:

  1. 组件化架构:职责单一的组件设计,便于鸿蒙跨端场景下的维护和复用
  2. 动画与交互优化:使用 Animated 实现流畅动画,通过位置校准确保 Popover 组件的交互体验
  3. 鸿蒙适配细节:通过 Base64 图标、Flex 布局、原生 API 封装等技巧,解决鸿蒙系统的兼容性问题
  4. 性能优化:FlatList 懒加载、图片懒加载、useNativeDriver 动画优化,提升鸿蒙设备的运行效率

真实演示案例代码:

// App.tsx
import React, { useState, useRef, useEffect } from 'react';
import { 
  View, 
  Text, 
  StyleSheet, 
  ScrollView, 
  SafeAreaView,
  Image,
  Dimensions,
  TouchableOpacity,
  Animated,
  FlatList,
  Modal
} from 'react-native';

// Base64 Icons for progress components
const PROGRESS_ICONS = {
  loading: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU1OjQ5KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTU6NDlaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjIwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6MjBaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjUxKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6NTFaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  menu: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjIyKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6MjJaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjUzKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6NTNaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
  share: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU4OjI0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTg6MjRaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......'
};

// 进度条组件
interface ProgressProps {
  value: number;
  maxValue?: number;
  height?: number;
  color?: string;
  backgroundColor?: string;
  borderRadius?: number;
  showPercentage?: boolean;
  animated?: boolean;
}

const ProgressBar: React.FC<ProgressProps> = ({
  value,
  maxValue = 100,
  height = 8,
  color = '#3b82f6',
  backgroundColor = '#334155',
  borderRadius = 4,
  showPercentage = true,
  animated = true
}) => {
  const [progress, setProgress] = useState(0);
  const progressAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    const percentage = (value / maxValue) * 100;
    
    if (animated) {
      Animated.timing(progressAnim, {
        toValue: percentage,
        duration: 500,
        useNativeDriver: false
      }).start();
    } else {
      setProgress(percentage);
    }
  }, [value, maxValue, animated]);

  const actualProgress = animated ? progressAnim : progress;

  return (
    <View style={[styles.progressBarContainer, { height, backgroundColor, borderRadius }]}>
      <Animated.View
        style={[
          styles.progressBarFill,
          {
            backgroundColor: color,
            borderRadius,
            width: animated ? actualProgress.interpolate({
              inputRange: [0, 100],
              outputRange: ['0%', '100%']
            }) : `${Math.min(100, Math.max(0, (value / maxValue) * 100))}%`
          }
        ]}
      />
      {showPercentage && (
        <Text style={styles.progressPercentage}>
          {Math.round((value / maxValue) * 100)}%
        </Text>
      )}
    </View>
  );
};

// 气泡弹出框组件
interface PopoverOption {
  id: string;
  label: string;
  icon?: string;
  action: () => void;
}

interface PopoverProps {
  visible: boolean;
  options: PopoverOption[];
  onClose: () => void;
  anchorPosition: { x: number; y: number };
}

const Popover: React.FC<PopoverProps> = ({ visible, options, onClose, anchorPosition }) => {
  const scaleAnim = useRef(new Animated.Value(0)).current;
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    if (visible) {
      Animated.parallel([
        Animated.spring(scaleAnim, {
          toValue: 1,
          tension: 300,
          friction: 20,
          useNativeDriver: true
        }),
        Animated.timing(fadeAnim, {
          toValue: 1,
          duration: 200,
          useNativeDriver: true
        })
      ]).start();
    } else {
      Animated.parallel([
        Animated.timing(scaleAnim, {
          toValue: 0,
          duration: 150,
          useNativeDriver: true
        }),
        Animated.timing(fadeAnim, {
          toValue: 0,
          duration: 150,
          useNativeDriver: true
        })
      ]).start();
    }
  }, [visible]);

  if (!visible) return null;

  return (
    <Modal transparent={true} animationType="none" visible={visible} onRequestClose={onClose}>
      <TouchableOpacity 
        style={styles.popoverBackdrop} 
        activeOpacity={1} 
        onPress={onClose}
      >
        <Animated.View 
          style={[
            styles.popoverContainer,
            {
              top: anchorPosition.y,
              left: anchorPosition.x,
              transform: [{ scale: scaleAnim }],
              opacity: fadeAnim
            }
          ]}
        >
          <View style={styles.popoverTriangle} />
          <View style={styles.popoverContent}>
            {options.map((option, index) => (
              <TouchableOpacity
                key={option.id}
                style={[styles.popoverItem, index === options.length - 1 && styles.lastItem]}
                onPress={() => {
                  option.action();
                  onClose();
                }}
              >
                {option.icon && (
                  <Image source={{ uri: option.icon }} style={styles.popoverItemIcon} />
                )}
                <Text style={styles.popoverItemText}>{option.label}</Text>
              </TouchableOpacity>
            ))}
          </View>
        </Animated.View>
      </TouchableOpacity>
    </Modal>
  );
};

// 懒加载图片组件
const LazyImage = ({ source, style }: { source: { uri: string }; style: any }) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  return (
    <View style={style}>
      {loading && !error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: '#334155', justifyContent: 'center', alignItems: 'center' }]}>
          <Image source={{ uri: PROGRESS_ICONS.loading }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
        </View>
      )}
      {error && (
        <View style={[StyleSheet.absoluteFill, { backgroundColor: '#dc2626', justifyContent: 'center', alignItems: 'center' }]}>
          <Image source={{ uri: PROGRESS_ICONS.error }} style={{ width: 30, height: 30, tintColor: '#ffffff' }} />
        </View>
      )}
      <Image
        source={source}
        style={StyleSheet.absoluteFill}
        onLoad={() => setLoading(false)}
        onError={() => {
          setLoading(false);
          setError(true);
        }}
        resizeMode="cover"
      />
    </View>
  );
};

// 通知项类型
interface NoticeItem {
  id: string;
  type: 'announcement' | 'warning' | 'info' | 'discount';
  title: string;
  content: string;
  time: string;
  read: boolean;
  progress: number;
  imageUrl?: string;
}

// 瀑布流通知项组件
const WaterfallNoticeItem = ({ notice, onLongPress }: { notice: NoticeItem; onLongPress: (notice: NoticeItem) => void }) => {
  const [anchorPosition, setAnchorPosition] = useState({ x: 0, y: 0 });
  const [showPopover, setShowPopover] = useState(false);

  const handleLongPress = (event: any) => {
    const { pageX, pageY } = event.nativeEvent;
    setAnchorPosition({ x: pageX - 150, y: pageY - 200 }); // Adjust position to center the popover
    setShowPopover(true);
  };

  const popoverOptions: PopoverOption[] = [
    {
      id: 'view-details',
      label: '查看详情',
      icon: PROGRESS_ICONS.settings,
      action: () => {
        alert(`正在查看 "${notice.title}" 的详情`);
      }
    },
    {
      id: 'share',
      label: '分享',
      icon: PROGRESS_ICONS.share,
      action: () => {
        alert(`正在分享 "${notice.title}"`);
      }
    },
    {
      id: 'more',
      label: '更多操作',
      icon: PROGRESS_ICONS.menu,
      action: () => {
        alert(`更多操作: "${notice.title}"`);
      }
    }
  ];

  return (
    <TouchableOpacity 
      style={styles.waterfallNoticeItem} 
      onLongPress={handleLongPress}
    >
      <View style={styles.waterfallNoticeHeader}>
        <View style={[styles.waterfallNoticeTypeBadge, { backgroundColor: `${getTypeColor(notice.type)}20` }]}>
          <Image 
            source={{ uri: getTypeIcon(notice.type) }} 
            style={[styles.waterfallNoticeTypeIcon, { tintColor: getTypeColor(notice.type) }]} 
          />
        </View>
        <View style={styles.waterfallNoticeInfo}>
          <Text style={styles.waterfallNoticeTitle}>{notice.title}</Text>
          <Text style={styles.waterfallNoticeTime}>{notice.time}</Text>
        </View>
        {!notice.read && <View style={styles.unreadDot} />}
      </View>
      
      <Text style={styles.waterfallNoticeContent}>{notice.content}</Text>
      
      <View style={styles.progressContainer}>
        <Text style={styles.progressLabel}>进度: {Math.round(notice.progress)}%</Text>
        <ProgressBar 
          value={notice.progress} 
          maxValue={100} 
          height={6} 
          color={getTypeColor(notice.type)}
          showPercentage={false}
        />
      </View>
      
      {notice.imageUrl && (
        <LazyImage
          source={{ uri: notice.imageUrl }}
          style={styles.waterfallNoticeImage}
        />
      )}
      
      <View style={styles.waterfallNoticeActions}>
        <TouchableOpacity style={styles.waterfallNoticeActionButton}>
          <Image source={{ uri: PROGRESS_ICONS.settings }} style={styles.waterfallNoticeActionIcon} />
          <Text style={styles.waterfallNoticeActionText}>设置</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.waterfallNoticeActionButton}>
          <Image source={{ uri: PROGRESS_ICONS.share }} style={styles.waterfallNoticeActionIcon} />
          <Text style={styles.waterfallNoticeActionText}>分享</Text>
        </TouchableOpacity>
      </View>
      
      <Popover
        visible={showPopover}
        options={popoverOptions}
        onClose={() => setShowPopover(false)}
        anchorPosition={anchorPosition}
      />
    </TouchableOpacity>
  );
};

// 类型辅助函数
const getTypeColor = (type: string) => {
  switch(type) {
    case 'announcement': return '#3b82f6';
    case 'warning': return '#ef4444';
    case 'info': return '#10b981';
    case 'discount': return '#f59e0b';
    default: return '#64748b';
  }
};

const getTypeIcon = (type: string) => {
  switch(type) {
    case 'announcement': return PROGRESS_ICONS.settings;
    case 'warning': return PROGRESS_ICONS.error;
    case 'info': return PROGRESS_ICONS.loading;
    case 'discount': return PROGRESS_ICONS.share;
    default: return PROGRESS_ICONS.loading;
  }
};

// 主应用组件
const App = () => {
  const [waterfallNotices, setWaterfallNotices] = useState<NoticeItem[]>([
    {
      id: '1',
      type: 'announcement',
      title: '文件下载进度',
      content: '正在下载大型软件包,请耐心等待...',
      time: '刚刚',
      read: false,
      progress: 65,
      imageUrl: 'https://picsum.photos/seed/1/300/200'
    },
    {
      id: '2',
      type: 'warning',
      title: '系统更新',
      content: '系统更新正在进行中,预计还需要15分钟完成',
      time: '2分钟前',
      read: false,
      progress: 30,
      imageUrl: 'https://picsum.photos/seed/2/300/400'
    },
    {
      id: '3',
      type: 'info',
      title: '备份任务',
      content: '正在备份您的照片和文档到云端',
      time: '5分钟前',
      read: true,
      progress: 85,
      imageUrl: 'https://picsum.photos/seed/3/300/300'
    },
    {
      id: '4',
      type: 'discount',
      title: '视频渲染',
      content: '正在处理您的视频文件,渲染完成后将自动保存',
      time: '8分钟前',
      read: false,
      progress: 42,
      imageUrl: 'https://picsum.photos/seed/4/300/250'
    },
    {
      id: '5',
      type: 'info',
      title: '数据同步',
      content: '正在同步您的联系人和日历数据',
      time: '10分钟前',
      read: true,
      progress: 95,
      imageUrl: 'https://picsum.photos/seed/5/300/350'
    },
    {
      id: '6',
      type: 'announcement',
      title: '文件上传',
      content: '正在上传您的文档到服务器',
      time: '15分钟前',
      read: true,
      progress: 20,
      imageUrl: 'https://picsum.photos/seed/6/300/200'
    }
  ]);

  // 模拟进度更新
  useEffect(() => {
    const interval = setInterval(() => {
      setWaterfallNotices(prev => 
        prev.map(notice => {
          // 只对未完成的任务更新进度
          if (notice.progress < 100) {
            const increment = Math.random() * 5; // 随机增加0-5%的进度
            const newProgress = Math.min(100, notice.progress + increment);
            
            return {
              ...notice,
              progress: newProgress
            };
          }
          return notice;
        })
      );
    }, 2000);

    return () => clearInterval(interval);
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>进度条组件</Text>
        <Text style={styles.headerSubtitle}>展示操作的当前进度</Text>
      </View>
      
      <ScrollView contentContainerStyle={styles.contentContainer}>
        <View style={styles.progressBarSection}>
          <Text style={styles.sectionTitle}>进度条示例</Text>
          
          <View style={styles.progressExample}>
            <Text style={styles.progressExampleLabel}>文件下载</Text>
            <ProgressBar value={75} maxValue={100} color="#3b82f6" />
          </View>
          
          <View style={styles.progressExample}>
            <Text style={styles.progressExampleLabel}>系统更新</Text>
            <ProgressBar value={45} maxValue={100} color="#ef4444" />
          </View>
          
          <View style={styles.progressExample}>
            <Text style={styles.progressExampleLabel}>数据备份</Text>
            <ProgressBar value={90} maxValue={100} color="#10b981" />
          </View>
          
          <View style={styles.progressExample}>
            <Text style={styles.progressExampleLabel}>视频渲染</Text>
            <ProgressBar value={30} maxValue={100} color="#f59e0b" />
          </View>
        </View>
        
        <View style={styles.waterfallSection}>
          <Text style={styles.sectionTitle}>瀑布流通知</Text>
          <Text style={styles.sectionSubtitle}>长按任意通知项查看操作菜单</Text>
          
          <FlatList
            data={waterfallNotices}
            renderItem={({ item }) => <WaterfallNoticeItem notice={item} onLongPress={() => {}} />}
            keyExtractor={item => item.id}
            numColumns={2}
            contentContainerStyle={styles.waterfallListContainer}
            showsVerticalScrollIndicator={false}
          />
        </View>
        
        <View style={styles.featuresSection}>
          <Text style={styles.featuresTitle}>功能特性</Text>
          <View style={styles.featureList}>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}></Text>
              <Text style={styles.featureText}>动态进度展示</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}></Text>
              <Text style={styles.featureText}>长按触发气泡菜单</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}></Text>
              <Text style={styles.featureText}>懒加载图片优化性能</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}></Text>
              <Text style={styles.featureText}>瀑布流布局展示</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}></Text>
              <Text style={styles.featureText}>丰富的Base64图标库</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}></Text>
              <Text style={styles.featureText}>响应式布局支持</Text>
            </View>
          </View>
        </View>
        
        <View style={styles.usageSection}>
          <Text style={styles.usageTitle}>使用说明</Text>
          <Text style={styles.usageText}>
            进度条组件适用于展示长时间操作的进度,
            如文件下载、上传、数据处理等场景。
          </Text>
        </View>
      </ScrollView>
      
      <View style={styles.footer}>
        <Text style={styles.footerText}>© 2023 进度条组件. All rights reserved.</Text>
      </View>
    </SafeAreaView>
  );
};

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#0f172a',
  },
  header: {
    backgroundColor: '#1e293b',
    paddingTop: 20,
    paddingBottom: 25,
    paddingHorizontal: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#334155',
  },
  headerTitle: {
    fontSize: 26,
    fontWeight: '700',
    color: '#f1f5f9',
    textAlign: 'center',
    marginBottom: 5,
  },
  headerSubtitle: {
    fontSize: 15,
    color: '#94a3b8',
    textAlign: 'center',
  },
  contentContainer: {
    padding: 16,
  },
  progressBarSection: {
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 22,
    fontWeight: '700',
    color: '#f1f5f9',
    marginBottom: 15,
    paddingLeft: 10,
    borderLeftWidth: 4,
    borderLeftColor: '#3b82f6',
  },
  progressExample: {
    marginBottom: 20,
  },
  progressExampleLabel: {
    fontSize: 14,
    color: '#cbd5e1',
    marginBottom: 8,
  },
  waterfallSection: {
    marginBottom: 20,
  },
  sectionSubtitle: {
    fontSize: 14,
    color: '#94a3b8',
    marginBottom: 15,
    paddingLeft: 14,
  },
  waterfallListContainer: {
    padding: 5,
  },
  waterfallNoticeItem: {
    backgroundColor: '#1e293b',
    margin: 5,
    borderRadius: 12,
    overflow: 'hidden',
    borderWidth: 1,
    borderColor: '#334155',
    position: 'relative',
  },
  waterfallNoticeHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
  },
  waterfallNoticeTypeBadge: {
    width: 30,
    height: 30,
    borderRadius: 15,
    justifyContent: 'center',
    alignItems: 'center',
    marginRight: 10,
  },
  waterfallNoticeTypeIcon: {
    width: 16,
    height: 16,
  },
  waterfallNoticeInfo: {
    flex: 1,
  },
  waterfallNoticeTitle: {
    fontSize: 14,
    fontWeight: '600',
    color: '#f1f5f9',
  },
  waterfallNoticeTime: {
    fontSize: 10,
    color: '#94a3b8',
  },
  waterfallNoticeContent: {
    fontSize: 12,
    color: '#cbd5e1',
    lineHeight: 18,
    paddingHorizontal: 12,
    marginBottom: 10,
  },
  progressContainer: {
    paddingHorizontal: 12,
    marginBottom: 10,
  },
  progressLabel: {
    fontSize: 12,
    color: '#94a3b8',
    marginBottom: 4,
  },
  waterfallNoticeImage: {
    height: 120,
    marginHorizontal: 12,
    marginBottom: 10,
    borderRadius: 8,
  },
  waterfallNoticeActions: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingVertical: 10,
    borderTopWidth: 1,
    borderTopColor: '#334155',
  },
  waterfallNoticeActionButton: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  waterfallNoticeActionIcon: {
    width: 14,
    height: 14,
    tintColor: '#94a3b8',
    marginRight: 4,
  },
  waterfallNoticeActionText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  unreadDot: {
    width: 8,
    height: 8,
    borderRadius: 4,
    backgroundColor: '#ef4444',
  },
  progressBarContainer: {
    backgroundColor: '#334155',
    overflow: 'hidden',
    position: 'relative',
  },
  progressBarFill: {
    height: '100%',
    backgroundColor: '#3b82f6',
  },
  progressPercentage: {
    position: 'absolute',
    right: 10,
    top: '50%',
    transform: [{ translateY: -10 }],
    color: '#ffffff',
    fontSize: 10,
    fontWeight: 'bold',
  },
  popoverBackdrop: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.5)',
  },
  popoverContainer: {
    position: 'absolute',
    zIndex: 1000,
  },
  popoverTriangle: {
    position: 'absolute',
    top: -10,
    left: 20,
    width: 0,
    height: 0,
    backgroundColor: 'transparent',
    borderStyle: 'solid',
    borderLeftWidth: 10,
    borderRightWidth: 10,
    borderBottomWidth: 10,
    borderLeftColor: 'transparent',
    borderRightColor: 'transparent',
    borderBottomColor: '#1e293b',
    transform: [{ rotate: '180deg' }],
  },
  popoverContent: {
    backgroundColor: '#1e293b',
    borderRadius: 8,
    paddingVertical: 4,
    minWidth: 150,
    borderWidth: 1,
    borderColor: '#334155',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.2,
    shadowRadius: 4,
    elevation: 5,
  },
  popoverItem: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#334155',
  },
  lastItem: {
    borderBottomWidth: 0,
  },
  popoverItemIcon: {
    width: 16,
    height: 16,
    tintColor: '#94a3b8',
    marginRight: 10,
  },
  popoverItemText: {
    fontSize: 14,
    color: '#f1f5f9',
  },
  featuresSection: {
    backgroundColor: '#1e293b',
    borderRadius: 16,
    padding: 20,
    marginBottom: 20,
    borderWidth: 1,
    borderColor: '#334155',
  },
  featuresTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#f1f5f9',
    marginBottom: 15,
    textAlign: 'center',
  },
  featureList: {
    paddingLeft: 10,
  },
  featureItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12,
  },
  featureBullet: {
    fontSize: 18,
    color: '#3b82f6',
    marginRight: 10,
  },
  featureText: {
    fontSize: 16,
    color: '#cbd5e1',
    flex: 1,
  },
  usageSection: {
    backgroundColor: '#1e293b',
    borderRadius: 16,
    padding: 20,
    borderWidth: 1,
    borderColor: '#334155',
  },
  usageTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#f1f5f9',
    marginBottom: 15,
    textAlign: 'center',
  },
  usageText: {
    fontSize: 16,
    color: '#cbd5e1',
    lineHeight: 24,
    textAlign: 'center',
  },
  footer: {
    paddingVertical: 15,
    alignItems: 'center',
    borderTopWidth: 1,
    borderTopColor: '#334155',
    backgroundColor: '#1e293b',
  },
  footerText: {
    fontSize: 14,
    color: '#94a3b8',
    fontWeight: '500',
  },
});

export default App;

在这里插入图片描述


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

在这里插入图片描述
本文探讨了React Native在鸿蒙跨端开发中的关键技术实现。以通知中心组件为例,重点分析了气泡弹出框(Popover)和懒加载图片两大核心组件的设计思路。
气泡弹出框通过Animated API实现流畅的缩放/淡入动画,利用Modal透明遮罩解决跨端适配问题,并采用动态位置校准确保鸿蒙设备上的准确定位。懒加载图片组件则实现了完整的加载状态管理,通过Base64图标和错误处理机制优化鸿蒙环境下的网络性能。
文章展示了如何利用React Native的跨平台特性,通过合理的组件划分、动画优化和状态管理,构建兼容鸿蒙系统的高质量交互组件。这些技术方案既保证了多端一致性,又针对鸿蒙系统进行了专项优化。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐