640 lines
20 KiB
TypeScript
640 lines
20 KiB
TypeScript
import { type Directory, Paths } from 'expo-file-system'
|
|
import * as FileSystem from 'expo-file-system/legacy'
|
|
import * as ImagePicker from 'expo-image-picker'
|
|
import * as MediaLibrary from 'expo-media-library'
|
|
import { router } from 'expo-router'
|
|
import { observer } from 'mobx-react-lite'
|
|
import React, { useEffect, useState } from 'react'
|
|
import { Button, StyleSheet, TextInput } from 'react-native'
|
|
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
|
|
|
|
import { imgPicker } from '@/@share/apis'
|
|
import { Block, SyncProgressToast, Toast, VideoBox } from '@/@share/components'
|
|
import { APP_VERSION } from '@/app.config'
|
|
import { BLE_UUIDS, PROTOCOL_VERSION } from '@/ble'
|
|
import { bleManager } from '@/ble/managers/bleManager'
|
|
import { ThemedText } from '@/components/themed-text'
|
|
import { ThemedView } from '@/components/themed-view'
|
|
import { bleStore, userStore } from '@/stores'
|
|
import { uploadFile } from '@/utils'
|
|
import { buildCdnUrl } from '@/utils/getCDNKey'
|
|
import { ensureCameraPermission } from '@/utils/permissions'
|
|
|
|
export default observer(function TabTwoScreen() {
|
|
const { user } = userStore
|
|
const {
|
|
isScanning,
|
|
// isConnected,
|
|
connectedDevice,
|
|
deviceInfo,
|
|
version,
|
|
isActivated,
|
|
transferProgress,
|
|
discoveredDevices,
|
|
loading,
|
|
error,
|
|
} = bleStore.state
|
|
|
|
const isConnected = bleStore.state.isConnected
|
|
|
|
// console.log('isConnected----------', isConnected)
|
|
|
|
const { galleryList: contents } = bleStore
|
|
|
|
const bleSessionId = bleStore.bleSessionId
|
|
|
|
const startScan = () => bleManager.startScan()
|
|
const stopScan = () => bleManager.stopScan()
|
|
const connectToDevice = (device: any) => bleManager.connectToDevice(device)
|
|
const disconnectDevice = () => bleManager.disconnectDevice()
|
|
const getDeviceInfo = () => bleManager.getDeviceInfo()
|
|
const getDeviceVersion = () => bleManager.getDeviceVersion()
|
|
const bindDevice = (userId: string) => bleManager.bindDevice(userId)
|
|
const unBindDevice = (userId: string) => bleManager.unBindDevice(userId)
|
|
const transferMediaSingle = (url: string) => bleManager.transferMediaSingle(url)
|
|
const deleteFile = (fileName: string) => bleManager.deleteFile(fileName)
|
|
|
|
const [imageUri, setImageUri] = useState(
|
|
'file:///data/user/0/com.bowong.duooomi/cache/ImageManipulator/153903d9-f5e9-4e65-9c7d-f7bedd574f3c.jpg',
|
|
)
|
|
const [userId, setUserId] = useState(user?.id ?? '01duHavK1CMW7pawcgOtB5aUqQeHPeni')
|
|
const [otaUrl, setOtaUrl] = useState(
|
|
userStore.scannedQR ?? 'https://cdn.roasmax.cn/upload/bf25206ab8644a8fb914aad5cf0fca08.bin',
|
|
)
|
|
const [comType, setComType] = useState('0x02') // 默认OTA_PACKAGE类型为128
|
|
|
|
useEffect(() => {
|
|
if (userStore.scannedQR) {
|
|
setOtaUrl(userStore.scannedQR ?? '')
|
|
}
|
|
}, [userStore.scannedQR])
|
|
|
|
// 查询设备版本
|
|
const queryDeviceVersion = async () => {
|
|
try {
|
|
await getDeviceVersion()
|
|
Toast?.show({ title: '版本查询请求已发送' })
|
|
} catch (error) {
|
|
console.error('Error querying version:', error)
|
|
Toast?.show({ title: '版本查询失败' })
|
|
}
|
|
}
|
|
|
|
// 请求设备信息
|
|
const requestDeviceInfo = async () => {
|
|
try {
|
|
await getDeviceInfo()
|
|
Toast?.show({ title: '设备信息查询请求已发送' })
|
|
} catch (error) {
|
|
console.error('Error requesting device info:', error)
|
|
Toast?.show({ title: `设备信息查询失败, ${error}` })
|
|
}
|
|
}
|
|
|
|
// 查询激活状态(通过获取设备信息来判断)
|
|
const queryActivationStatus = async () => {
|
|
try {
|
|
await getDeviceInfo()
|
|
Toast?.show({ title: '激活状态查询请求已发送' })
|
|
} catch (error) {
|
|
console.error('Error querying activation:', error)
|
|
Toast?.show({ title: `激活状态查询失败, ${error}` })
|
|
}
|
|
}
|
|
|
|
// 绑定设备
|
|
const handleBindDevice = async () => {
|
|
try {
|
|
await bindDevice(userId)
|
|
Toast?.show({ title: '设备绑定请求已发送' })
|
|
} catch (error) {
|
|
console.error('Error binding device:', error)
|
|
Toast?.show({ title: `设备绑定失败, ${error}` })
|
|
}
|
|
}
|
|
|
|
// 解绑设备
|
|
const handleUnbindDevice = async () => {
|
|
try {
|
|
await unBindDevice(userId)
|
|
Toast?.show({ title: '设备解绑请求已发送' })
|
|
} catch (error) {
|
|
console.error('Error unbinding device:', error)
|
|
Toast?.show({ title: `设备解绑失败, ${error}` })
|
|
}
|
|
}
|
|
|
|
const handleOtaUpgrade = async () => {
|
|
try {
|
|
if (!otaUrl?.trim()) {
|
|
Toast?.show({ title: '请输入有效的OTA地址' })
|
|
return
|
|
}
|
|
|
|
bleStore.setState((prestate) => {
|
|
return { ...prestate, transferProgress: 0 }
|
|
})
|
|
|
|
Toast.show({
|
|
renderContent: () => <SyncProgressToast title="OTA升级中" />,
|
|
duration: 0,
|
|
})
|
|
const buffer = await bleManager.performOtaUpgrade(otaUrl, comType)
|
|
|
|
Toast.hide()
|
|
Toast?.show({ title: `OTA升级完成 (${buffer.byteLength} bytes) `, duration: 2e3 })
|
|
} catch (error) {
|
|
console.error('OTA upgrade failed:', error)
|
|
const msg = typeof error === 'string' ? error : (error as any)?.message || 'OTA升级失败'
|
|
Toast?.show({ title: msg })
|
|
} finally {
|
|
Toast.hide()
|
|
}
|
|
}
|
|
|
|
const convertToANIAsBuffer = async (
|
|
tempDir: Directory,
|
|
media: ImagePicker.ImagePickerAsset,
|
|
): Promise<ArrayBuffer> => {
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('file', {
|
|
uri: media.uri,
|
|
name: media.fileName || 'video.mp4',
|
|
type: media.mimeType || 'video/mp4',
|
|
} as any)
|
|
|
|
const aniTest = `https://bowongai-test--ani-video-converter-fastapi-app.modal.run/api/convert/ani`
|
|
const aniProd = `https://bowongai-prod--ani-video-converter-fastapi-app.modal.run/api/convert/ani`
|
|
const response = await fetch(aniProd, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: { Accept: 'multipart/form-data' },
|
|
})
|
|
if (!response.ok) {
|
|
throw new Error(`Conversion failed with status ${response.status}`)
|
|
}
|
|
const content = await response.arrayBuffer()
|
|
console.debug(`Converted video size : ${content.byteLength} bytes`)
|
|
return content
|
|
} catch (error) {
|
|
console.error('Error converting video to ANI:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
const handleUploadMedia = async () => {
|
|
try {
|
|
const assetList = await imgPicker({ maxImages: 1, type: ImagePicker.MediaTypeOptions.All, resultType: 'asset' })
|
|
|
|
const asset = assetList[0] as ImagePicker.ImagePickerAsset
|
|
if (!asset) {
|
|
console.warn('No asset selected')
|
|
return
|
|
}
|
|
|
|
// return
|
|
if (asset?.uri) {
|
|
Toast.showLoading({ title: '文件传输中...', duration: 0 })
|
|
setImageUri(asset.uri)
|
|
|
|
const url = await uploadFile({
|
|
uri: asset.uri,
|
|
mimeType: asset.mimeType,
|
|
fileName: asset.fileName ?? undefined,
|
|
})
|
|
|
|
transferMediaSingle(url)
|
|
.then(() => {
|
|
Toast?.show({ title: '文件传输完成' })
|
|
})
|
|
.catch((error) => {
|
|
const { status } = error || {}
|
|
if (!status) {
|
|
Toast?.show({ title: `文件传输失败` })
|
|
return
|
|
}
|
|
|
|
if (status === 'no_space') {
|
|
Toast?.show({ title: '文件传输失败, 设备存储空间不足' })
|
|
}
|
|
if (status === 'duplicated') {
|
|
Toast?.show({ title: '文件传输失败, 设备已存在相同文件' })
|
|
}
|
|
})
|
|
.finally(() => {
|
|
Toast.hideLoading()
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Error uploading media:', error)
|
|
}
|
|
}
|
|
|
|
const downloadFile = async () => {
|
|
try {
|
|
const perm = await MediaLibrary.requestPermissionsAsync()
|
|
if (perm.status !== 'granted') {
|
|
Toast?.show({ title: '请开启相册权限' })
|
|
return
|
|
}
|
|
const filename = assetFileName || 'image.jpg'
|
|
let localUri = imageUri
|
|
if (!localUri.startsWith('file://')) {
|
|
const target = `${Paths.documentDirectory}${filename}`
|
|
const res = await FileSystem.downloadAsync(imageUri, target)
|
|
localUri = res.uri
|
|
}
|
|
await MediaLibrary.saveToLibraryAsync(localUri)
|
|
console.log('已保存到系统相册:', localUri)
|
|
Toast?.show({ title: '保存成功' })
|
|
} catch (error) {
|
|
console.error('保存失败:', error)
|
|
Toast?.show({ title: '保存失败' })
|
|
}
|
|
}
|
|
const renderImage = () => {
|
|
if (!imageUri) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<Block style={{ width: BLE_UUIDS.SCREEN_SIZE, height: BLE_UUIDS.SCREEN_SIZE }} onClick={downloadFile}>
|
|
<VideoBox url={imageUri} style={{ width: BLE_UUIDS.SCREEN_SIZE, height: BLE_UUIDS.SCREEN_SIZE }} />
|
|
</Block>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<KeyboardAwareScrollView
|
|
bottomOffset={100}
|
|
contentContainerStyle={{ paddingHorizontal: 20, paddingTop: 40, paddingBottom: 200, backgroundColor: 'white' }}
|
|
>
|
|
<ThemedView style={styles.titleContainer}>
|
|
<ThemedText type="title">BLE Explorer</ThemedText>
|
|
<ThemedText>插件当前版本号: {APP_VERSION}</ThemedText>
|
|
<ThemedText>Session ID: {bleSessionId}</ThemedText>
|
|
<TextInput
|
|
editable={true}
|
|
placeholder=""
|
|
placeholderTextColor="#999"
|
|
style={styles.input}
|
|
value={bleSessionId}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<Button
|
|
title="Try!"
|
|
onPress={() => {
|
|
// Sentry.captureMessage('Test from Android Build')
|
|
throw new Error('Test Sentry Integration')
|
|
// Sentry.captureException('useUpdateCheckerError11', {
|
|
// level: 'error',
|
|
// tags: {
|
|
// useUpdateChecker: 'checkForUpdateAsync_failed',
|
|
// component: 'useUpdateChecker',
|
|
// },
|
|
// contexts: {
|
|
// update: {
|
|
// __DEV__: __DEV__,
|
|
// },
|
|
// },
|
|
// })
|
|
}}
|
|
/>
|
|
|
|
{/* Connection Status */}
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">Connection Status</ThemedText>
|
|
<ThemedText>Scanning: {isScanning ? 'Yes' : 'No'}</ThemedText>
|
|
<ThemedText>Connected: {isConnected ? 'Yes' : 'No'}</ThemedText>
|
|
<ThemedText>Device: {connectedDevice?.name || 'None'}</ThemedText>
|
|
{error && <ThemedText style={styles.errorText}>Error: {error}</ThemedText>}
|
|
</ThemedView>
|
|
|
|
{/* Discovered Devices */}
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">Discovered Devices</ThemedText>
|
|
<ThemedText style={styles.deviceCount}>Total devices found: {discoveredDevices.length}</ThemedText>
|
|
{discoveredDevices.length === 0 ? (
|
|
<ThemedText>No devices discovered yet. Start scanning to find devices.</ThemedText>
|
|
) : (
|
|
<ThemedView style={{ gap: 8 }}>
|
|
{discoveredDevices.map((item: any) => (
|
|
<ThemedView
|
|
key={item.id}
|
|
darkColor="#2a2a2a"
|
|
lightColor="#eee"
|
|
style={[styles.deviceItem, item.connected && styles.connectedDevice]}
|
|
>
|
|
<ThemedView darkColor="transparent" lightColor="transparent" style={styles.deviceInfo}>
|
|
<ThemedText style={item.connected && styles.connectedDeviceText}>
|
|
{item.name || 'Unknown Device'}
|
|
</ThemedText>
|
|
<ThemedText style={styles.deviceId}>{item.id}</ThemedText>
|
|
{item.serviceUUIDs && item.serviceUUIDs.length > 0 && (
|
|
<ThemedText style={styles.serviceUuids}>Services: {item.serviceUUIDs.join(', ')}</ThemedText>
|
|
)}
|
|
{item.connected && <ThemedText style={styles.connectionStatus}>Connected</ThemedText>}
|
|
</ThemedView>
|
|
<Button
|
|
disabled={isConnected || loading.connecting || item.connected}
|
|
title={loading.connecting ? 'Connecting...' : item.connected ? 'Connected' : 'Connect'}
|
|
onPress={() => connectToDevice(item)}
|
|
/>
|
|
</ThemedView>
|
|
))}
|
|
</ThemedView>
|
|
)}
|
|
</ThemedView>
|
|
|
|
{/* Device Info */}
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">Device Information</ThemedText>
|
|
<ThemedText>Activated: {isActivated ? 'Yes' : 'No'}</ThemedText>
|
|
<ThemedText>Version: {JSON.stringify(version) || 'Unknown'}</ThemedText>
|
|
{deviceInfo && (
|
|
<>
|
|
<ThemedText>Name: {deviceInfo.name}</ThemedText>
|
|
<ThemedText>Total Space: {deviceInfo.allspace} KB</ThemedText>
|
|
<ThemedText>Free Space: {deviceInfo.freespace} KB</ThemedText>
|
|
<ThemedText>Brand: {deviceInfo.brand}</ThemedText>
|
|
</>
|
|
)}
|
|
</ThemedView>
|
|
|
|
{/* Transfer Status */}
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">File Transfer</ThemedText>
|
|
<ThemedText>Converting: {loading.converting ? 'Yes' : 'No'}</ThemedText>
|
|
<ThemedText>Transferring: {loading.transferring ? 'Yes' : 'No'}</ThemedText>
|
|
<ThemedText>Progress: {transferProgress}%</ThemedText>
|
|
</ThemedView>
|
|
|
|
{/* {renderImage()} */}
|
|
|
|
{/* Control Buttons */}
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">Controls</ThemedText>
|
|
<ThemedView style={styles.buttonRow}>
|
|
<Button title={isScanning ? '停止扫描' : '开始扫描'} onPress={isScanning ? stopScan : startScan} />
|
|
<Button disabled={!isConnected} title="断开连接" onPress={disconnectDevice} />
|
|
</ThemedView>
|
|
|
|
<ThemedView style={styles.buttonRow}>
|
|
<Button
|
|
disabled={!isConnected}
|
|
title={loading.querying ? '获取中...' : '获取版本号'}
|
|
onPress={queryDeviceVersion}
|
|
/>
|
|
<Button
|
|
disabled={!isConnected}
|
|
title={loading.querying ? '获取中...' : '获取设备信息'}
|
|
onPress={requestDeviceInfo}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={styles.buttonRow}>
|
|
<Button
|
|
disabled={!isConnected || loading.transferring}
|
|
title={loading.transferring ? '传输中...' : '发文件到设备'}
|
|
onPress={handleUploadMedia}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={{ marginTop: 8 }}>
|
|
<ThemedText style={{ marginBottom: 4 }}>User ID:</ThemedText>
|
|
<TextInput
|
|
placeholder="输入用户ID"
|
|
style={styles.input}
|
|
value={userId}
|
|
onChangeText={setUserId}
|
|
placeholderTextColor="#999"
|
|
// editable={isConnected}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={styles.buttonRow}>
|
|
<Button
|
|
disabled={!isConnected || isActivated || !userId.trim()}
|
|
title="绑定设备"
|
|
onPress={handleBindDevice}
|
|
/>
|
|
<Button
|
|
disabled={!isConnected || !isActivated || !userId.trim()}
|
|
title="解绑设备"
|
|
onPress={handleUnbindDevice}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={{ marginTop: 8 }}>
|
|
<ThemedText style={{ marginBottom: 4 }}>蓝牙头设置 Command Type:</ThemedText>
|
|
<TextInput
|
|
placeholder="蓝牙头"
|
|
style={styles.input}
|
|
value={comType}
|
|
onChangeText={setComType}
|
|
placeholderTextColor="#999"
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={{ marginTop: 8 }}>
|
|
<ThemedText style={{ marginBottom: 4 }}>OTA URL:</ThemedText>
|
|
<TextInput
|
|
placeholder="ota包地址"
|
|
style={styles.input}
|
|
value={otaUrl}
|
|
onChangeText={setOtaUrl}
|
|
placeholderTextColor="#999"
|
|
/>
|
|
<Button
|
|
title="扫描二维码"
|
|
onPress={async () => {
|
|
const ok = await ensureCameraPermission('需要相机权限用于扫码,请在设置中开启')
|
|
if (ok) {
|
|
router.push('/scan')
|
|
}
|
|
}}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={styles.buttonRow}>
|
|
<Button
|
|
disabled={!isConnected || loading.transferring || !otaUrl.trim()}
|
|
title={loading.transferring ? 'OTA升级中...' : 'OTA升级'}
|
|
onPress={handleOtaUpgrade}
|
|
/>
|
|
</ThemedView>
|
|
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">设备文件列表 ({contents.length})</ThemedText>
|
|
<ThemedView style={{ gap: 8 }}>
|
|
{
|
|
// Array(5)
|
|
// .fill('https://cdn.roasmax.cn/material/8836b879f2d44af48eef8da82d13a755.mp4')
|
|
contents.map((item, index) => {
|
|
// console.log('item-----------', item)
|
|
const url = buildCdnUrl(item)
|
|
if (!url) return null
|
|
const Width = 100
|
|
return (
|
|
<Block key={`${item}-${index}`} className="gap-2">
|
|
<ThemedText style={styles.contentText} numberOfLines={1}>
|
|
{item}
|
|
</ThemedText>
|
|
<VideoBox style={{ width: Width, height: Width }} url={url} />
|
|
<Button
|
|
title="删除"
|
|
color="red"
|
|
disabled={!isConnected}
|
|
onPress={async () => {
|
|
try {
|
|
await deleteFile(item)
|
|
Toast?.show({ title: '删除请求已发送' })
|
|
} catch (error) {
|
|
console.error('Error deleting file:', error)
|
|
Toast?.show({ title: `删除失败: ${error}` })
|
|
}
|
|
}}
|
|
/>
|
|
</Block>
|
|
)
|
|
})
|
|
}
|
|
</ThemedView>
|
|
</ThemedView>
|
|
</ThemedView>
|
|
|
|
{/* Protocol Info - Available in both modes */}
|
|
<ThemedView style={styles.section}>
|
|
<ThemedText type="subtitle">Protocol Information</ThemedText>
|
|
<ThemedText>Version: {PROTOCOL_VERSION}</ThemedText>
|
|
<ThemedText>Service UUID: {BLE_UUIDS.SERVICE}</ThemedText>
|
|
<ThemedText>Write UUID: {BLE_UUIDS.WRITE_CHARACTERISTIC}</ThemedText>
|
|
<ThemedText>Read UUID: {BLE_UUIDS.READ_CHARACTERISTIC}</ThemedText>
|
|
</ThemedView>
|
|
</KeyboardAwareScrollView>
|
|
)
|
|
})
|
|
|
|
const styles = StyleSheet.create({
|
|
headerImage: {
|
|
color: '#808080',
|
|
bottom: -90,
|
|
left: -35,
|
|
position: 'absolute',
|
|
},
|
|
titleContainer: {
|
|
flexDirection: 'column',
|
|
gap: 8,
|
|
},
|
|
section: {
|
|
gap: 8,
|
|
marginBottom: 8,
|
|
},
|
|
buttonRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 8,
|
|
gap: 8,
|
|
},
|
|
logHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 8,
|
|
},
|
|
logContainer: {
|
|
maxHeight: 200,
|
|
padding: 8,
|
|
borderRadius: 4,
|
|
},
|
|
logText: {
|
|
fontSize: 12,
|
|
marginBottom: 2,
|
|
},
|
|
deviceItem: {
|
|
padding: 12,
|
|
marginBottom: 8,
|
|
borderRadius: 8,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
},
|
|
deviceInfo: {
|
|
flex: 1,
|
|
marginRight: 12,
|
|
},
|
|
deviceId: {
|
|
fontSize: 12,
|
|
opacity: 0.7,
|
|
},
|
|
deviceCount: {
|
|
fontSize: 14,
|
|
opacity: 0.8,
|
|
marginTop: 4,
|
|
marginBottom: 8,
|
|
fontWeight: '500',
|
|
},
|
|
serviceUuids: {
|
|
fontSize: 11,
|
|
opacity: 0.6,
|
|
marginTop: 2,
|
|
fontStyle: ' ',
|
|
},
|
|
connectionStatus: {
|
|
fontSize: 12,
|
|
fontWeight: 'bold',
|
|
marginTop: 4,
|
|
},
|
|
connectedDevice: {
|
|
borderWidth: 1,
|
|
borderColor: '#4CAF50',
|
|
},
|
|
connectedDeviceText: {
|
|
fontWeight: 'bold',
|
|
},
|
|
errorText: {
|
|
marginTop: 8,
|
|
fontWeight: 'bold',
|
|
},
|
|
modeSwitchContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginVertical: 12,
|
|
},
|
|
modeLabel: {
|
|
fontSize: 16,
|
|
marginHorizontal: 8,
|
|
},
|
|
modeDescription: {
|
|
fontSize: 12,
|
|
opacity: 0.7,
|
|
marginTop: 8,
|
|
},
|
|
input: {
|
|
borderWidth: 1,
|
|
borderColor: '#ccc',
|
|
borderRadius: 4,
|
|
padding: 8,
|
|
fontSize: 14,
|
|
backgroundColor: '#fff',
|
|
color: '#000',
|
|
},
|
|
contentItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
padding: 8,
|
|
backgroundColor: '#f5f5f5',
|
|
borderRadius: 4,
|
|
gap: 8,
|
|
},
|
|
contentText: {
|
|
flex: 1,
|
|
fontSize: 12,
|
|
color: '#333',
|
|
},
|
|
})
|