完善自定义源

This commit is contained in:
lyswhut 2023-11-04 12:26:56 +08:00
parent 35ecd2022b
commit 1a97295c7b
14 changed files with 470 additions and 307 deletions

View File

@ -104,14 +104,14 @@ globalThis.lx_setup = (key, id, name, description, rawScript) => {
request: 'request',
cancelRequest: 'cancelRequest',
response: 'response',
'utils.crypto.aesEncrypt': 'utils.crypto.aesEncrypt',
'utils.crypto.rsaEncrypt': 'utils.crypto.rsaEncrypt',
'utils.crypto.randomBytes': 'utils.crypto.randomBytes',
'utils.crypto.md5': 'utils.crypto.md5',
'utils.buffer.from': 'utils.buffer.from',
'utils.buffer.bufToString': 'utils.buffer.bufToString',
'utils.zlib.inflate': 'utils.zlib.inflate',
'utils.zlib.deflate': 'utils.zlib.deflate',
// 'utils.crypto.aesEncrypt': 'utils.crypto.aesEncrypt',
// 'utils.crypto.rsaEncrypt': 'utils.crypto.rsaEncrypt',
// 'utils.crypto.randomBytes': 'utils.crypto.randomBytes',
// 'utils.crypto.md5': 'utils.crypto.md5',
// 'utils.buffer.from': 'utils.buffer.from',
// 'utils.buffer.bufToString': 'utils.buffer.bufToString',
// 'utils.zlib.inflate': 'utils.zlib.inflate',
// 'utils.zlib.deflate': 'utils.zlib.deflate',
}
const EVENT_NAMES = Object.freeze({
request: 'request',

View File

@ -3,10 +3,10 @@ import { init as initLog } from '@/utils/log'
import { bootLog, getBootLog } from '@/utils/bootLog'
import '@/config/globalData'
import { getFontSize } from '@/utils/data'
import { Alert } from 'react-native'
import { exitApp } from './utils/nativeModules/utils'
import { windowSizeTools } from './utils/windowSizeTools'
import { listenLaunchEvent } from './navigation/regLaunchedEvent'
import { tipDialog } from './utils/tools'
console.log('starting app...')
listenLaunchEvent()
@ -33,15 +33,13 @@ void Promise.all([getFontSize(), windowSizeTools.init()]).then(async([fontSize])
try {
handlePushedHomeScreen = await init()
} catch (err: any) {
Alert.alert('初始化失败 (Init Failed)', `Boot Log:\n${tryGetBootLog()}\n\n${(err.stack ?? err.message) as string}`, [
{
text: 'Exit',
onPress() {
exitApp()
},
},
], {
cancelable: false,
void tipDialog({
title: '初始化失败 (Init Failed)',
message: `Boot Log:\n${tryGetBootLog()}\n\n${(err.stack ?? err.message) as string}`,
btnText: 'Exit',
bgClose: false,
}).then(() => {
exitApp()
})
return
}
@ -57,27 +55,23 @@ void Promise.all([getFontSize(), windowSizeTools.init()]).then(async([fontSize])
await navigations.pushHomeScreen().then(() => {
void handlePushedHomeScreen()
}).catch((err: any) => {
Alert.alert('Error', err.message, [
{
text: 'Exit',
onPress() {
exitApp()
},
},
], {
cancelable: false,
void tipDialog({
title: 'Error',
message: err.message,
btnText: 'Exit',
bgClose: false,
}).then(() => {
exitApp()
})
})
})
}).catch((err) => {
Alert.alert('初始化失败 (Init Failed)', `Boot Log:\n\n${(err.stack ?? err.message) as string}`, [
{
text: 'Exit',
onPress() {
exitApp()
},
},
], {
cancelable: false,
void tipDialog({
title: '初始化失败 (Init Failed)',
message: `Boot Log:\n\n${(err.stack ?? err.message) as string}`,
btnText: 'Exit',
bgClose: false,
}).then(() => {
exitApp()
})
})

View File

@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { View, TouchableOpacity, Alert } from 'react-native'
import { View, TouchableOpacity } from 'react-native'
import CheckBox from './Checkbox'
import { createStyle } from '@/utils/tools'
import { createStyle, tipDialog } from '@/utils/tools'
import { scaleSizeH, scaleSizeW } from '@/utils/pixelRatio'
import { useTheme } from '@/store/theme/hook'
import Text from '../Text'
@ -54,15 +54,11 @@ export default ({ check, label, children, onChange, helpTitle, helpDesc, disable
const helpComponent = useMemo(() => {
const handleShowHelp = () => {
Alert.alert(helpTitle ?? '', helpDesc,
// [{
// text: '我知道了 (Close)',
// // onPress: () => {
// // void saveData(storageDataPrefix.cheatTip, true)
// // resolve()
// // },
// }],
)
void tipDialog({
title: helpTitle ?? '',
message: helpDesc,
btnText: global.i18n.t('understand'),
})
}
return (helpTitle ?? helpDesc) ? (
<TouchableOpacity style={styles.helpBtn} onPress={handleShowHelp}>

View File

@ -4,8 +4,7 @@ import { getData, removeData, saveData } from '@/plugins/storage'
import migrateSetting from './migrateSetting'
import settingState from '@/store/setting/state'
import { migrateMetaData, migrateListData } from './migrate'
import { Alert } from 'react-native'
import { exitApp } from '@/utils/tools'
import { exitApp, tipDialog } from '@/utils/tools'
// 业务相关工具方法
@ -90,15 +89,13 @@ export const initSetting = async() => {
await migrateListData()
await migrateMetaData()
} catch (err: any) {
Alert.alert('数据迁移失败 (Migrate data Failed)', `请加企鹅群(830125506)或到GitHub反馈为了防止数据丢失应用将停止运行错误信息\n${(err.stack ?? err.message) as string}`, [
{
text: 'Exit',
onPress() {
exitApp()
},
},
], {
cancelable: false,
void tipDialog({
title: '数据迁移失败 (Migrate data Failed)',
message: `请加企鹅群(830125506)或到GitHub反馈为了防止数据丢失应用将停止运行错误信息\n${(err.stack ?? err.message) as string}`,
btnText: 'Exit',
bgClose: false,
}).then(() => {
exitApp()
})
throw err
}

View File

@ -1,9 +1,10 @@
import { type InitParams, onScriptAction, sendAction, type ResponseParams, type UpdateInfoParams } from '@/utils/nativeModules/userApi'
import { log, setUserApiList, setUserApiStatus } from '../userApi'
import { log, setUserApiList, setUserApiStatus } from '@/core/userApi'
import settingState from '@/store/setting/state'
import BackgroundTimer from 'react-native-background-timer'
import { httpFetch } from '@/utils/request'
import { fetchData } from './request'
import { getUserApiList } from '@/utils/data'
import { confirmDialog, openUrl, tipDialog } from '@/utils/tools'
export default async(setting: LX.AppSetting) => {
@ -90,26 +91,26 @@ export default async(setting: LX.AppSetting) => {
global.state_event.apiSourceUpdated(settingState.setting['common.apiSource'])
}
const showUpdateAlert = ({ name, log, updateUrl }: UpdateInfoParams) => {
// if (updateUrl) {
// void dialog({
// message: `${t('user_api__update_alert', { name })}\n${log}`,
// selection: true,
// showCancel: true,
// confirmButtonText: t('user_api__update_alert_open_url'),
// cancelButtonText: t('close'),
// }).then(confirm => {
// if (!confirm) return
// window.setTimeout(() => {
// void openUrl(updateUrl)
// }, 300)
// })
// } else {
// void dialog({
// message: `${t('user_api__update_alert', { name })}\n${log}`,
// selection: true,
// confirmButtonText: t('ok'),
// })
// }
if (updateUrl) {
void confirmDialog({
message: `${global.i18n.t('user_api_update_alert', { name })}\n${log}`,
// selection: true,
// showCancel: true,
confirmButtonText: global.i18n.t('user_api_update_alert_open_url'),
cancelButtonText: global.i18n.t('close'),
}).then(confirm => {
if (!confirm) return
setTimeout(() => {
void openUrl(updateUrl)
}, 300)
})
} else {
void tipDialog({
message: `${global.i18n.t('user_api_update_alert', { name })}\n${log}`,
// selection: true,
btnText: global.i18n.t('ok'),
})
}
}
onScriptAction((event) => {
@ -122,11 +123,7 @@ export default async(setting: LX.AppSetting) => {
handleUserApiResponse(event.data)
break
case 'request':
httpFetch(event.data.url, {
...event.data.options,
credentials: 'omit',
cache: 'default',
}).promise.then(response => {
fetchData(event.data.url, event.data.options).request.then(response => {
// console.log(response)
sendAction('response', {
error: null,

View File

@ -0,0 +1,122 @@
// import needle from 'needle'
// import progress from 'request-progress'
import BackgroundTimer from 'react-native-background-timer'
const defaultHeaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
}
// var proxyUrl = "http://" + user + ":" + password + "@" + host + ":" + port;
// var proxiedRequest = request.defaults({'proxy': proxyUrl});
const handleRequestData = async({
method = 'get',
headers = {},
format = 'json',
credentials = 'omit',
cache = 'default',
...options
}) => {
// console.log(url, options)
headers = Object.assign({
Accept: 'application/json',
}, headers)
if (method.toLocaleLowerCase() === 'post' && !headers['Content-Type']) {
if (options.form) {
headers['Content-Type'] = 'application/x-www-form-urlencoded'
const formBody = []
for (let [key, value] of Object.entries(options.form)) {
let encodedKey = encodeURIComponent(key)
let encodedValue = encodeURIComponent(value)
formBody.push(`${encodedKey}=${encodedValue}`)
}
options.body = formBody.join('&')
delete options.form
} else if (options.formData) {
headers['Content-Type'] = 'multipart/form-data'
const formBody = []
for (let [key, value] of Object.entries(options.form)) {
let encodedKey = encodeURIComponent(key)
let encodedValue = encodeURIComponent(value)
formBody.push(`${encodedKey}=${encodedValue}`)
}
options.body = options.formData
delete options.formData
} else {
headers['Content-Type'] = 'application/json'
}
}
if (headers['Content-Type'] === 'application/json' && options.body) {
options.body = JSON.stringify(options.body)
}
return {
...options,
method,
credentials,
cache,
headers: Object.assign({}, defaultHeaders, headers),
}
}
// https://stackoverflow.com/a/64945178
const blobToBuffer = (blob) => {
return new Promise((resolve, reject) => {
const reader = new global.FileReader()
reader.onerror = reject
reader.onload = () => {
const data = reader.result.slice(reader.result.indexOf('base64,') + 7)
resolve(Buffer.from(data, 'base64'))
}
reader.readAsDataURL(blob)
})
}
export const fetchData = (url, { timeout = 15000, ...options }) => {
// console.log('---start---', url)
const controller = new global.AbortController()
let id = BackgroundTimer.setTimeout(() => {
id = null
controller.abort()
}, timeout)
return {
request: handleRequestData(options).then(options => {
return global.fetch(url, {
...options,
signal: controller.signal,
}).then(resp => (options.binary ? resp.blob() : resp.text()).then(text => {
// console.log(options, headers, text)
return {
headers: resp.headers.map,
body: text,
statusCode: resp.status,
statusMessage: resp.statusText,
url: resp.url,
ok: resp.ok,
}
})).then(resp => {
if (options.binary) {
return blobToBuffer(resp.body).then(buffer => {
resp.body = buffer
return resp
})
} else {
try {
resp.body = JSON.parse(resp.body)
} catch {}
return resp
}
}).catch(err => {
// console.log(err, err.code, err.message)
return Promise.reject(err)
}).finally(() => {
if (id == null) return
BackgroundTimer.clearTimeout(id)
})
}),
abort() {
controller.abort()
},
}
}

View File

@ -33,9 +33,7 @@ export const importUserApi = async(script: string) => {
}
export const removeUserApi = async(ids: string[]) => {
console.log(ids)
const list = await removeUserApiFromStore(ids)
console.log(list)
action.setUserApiList(list)
}

View File

@ -388,14 +388,19 @@
"timeout_exit_tip_on": "Stop playing after {time}",
"toggle_source_failed": "Failed to change the source, please try to manually search for the song in other sources to play",
"toggle_source_try": "Try switching to another source...",
"understand": "Already understood 👌",
"user_api_allow_show_update_alert": "Allow update popups to be displayed",
"user_api_btn_import": "Import",
"user_api_empty": "Its actually empty here 😲",
"user_api_import_desc": "Select custom source file",
"user_api_import_failed_tip": "Import failed",
"user_api_max_tip": "A maximum of 20 sources can exist at the same time🤪\n\nIf you want to continue importing, please remove some old sources to make room.",
"user_api_note": "Tip: Although we have isolated the running environment of the script as much as possible, importing scripts containing malicious behaviors may still affect your system, so please import with caution.",
"user_api_readme": "Source writing instructions: ",
"user_api_remove_tip": "Do you really want to remove {name}?",
"user_api_title": "Custom source management",
"user_api_title": "Custom source management (experimental)",
"user_api_update_alert": "Custom source [{name}] found new version",
"user_api_update_alert_open_url": "Open update address",
"version_btn_close": "Close",
"version_btn_downloading": "I am trying to download...{total}/{current} ({progress}%)",
"version_btn_failed": "Retry",

View File

@ -388,14 +388,19 @@
"timeout_exit_tip_on": "{time} 后停止播放",
"toggle_source_failed": "换源失败,请尝试手动在其他源搜索该歌曲播放",
"toggle_source_try": "尝试切换到其他源...",
"understand": "已了解 👌",
"user_api_allow_show_update_alert": "允许显示更新弹窗",
"user_api_btn_import": "导入",
"user_api_empty": "这里竟然是空的 😲",
"user_api_import_desc": "选择自定义源文件",
"user_api_import_failed_tip": "导入失败",
"user_api_max_tip": "最多只能同时存在20个源哦🤪\n想要继续导入的话请先移除一些旧的源腾出位置吧",
"user_api_note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
"user_api_readme": "源编写说明:",
"user_api_remove_tip": "你真的要移除 {name} 吗?",
"user_api_title": "自定义源管理",
"user_api_title": "自定义源管理(实验性)",
"user_api_update_alert": "自定义源 [{name}] 发现新版本",
"user_api_update_alert_open_url": "打开更新地址",
"version_btn_close": "关闭",
"version_btn_downloading": "正在努力下载中...{total}/{current} ({progress}%)",
"version_btn_failed": "重试",

View File

@ -117,75 +117,76 @@ export function pushPlayDetailScreen(componentId: string) {
},
})
*/
void InteractionManager.runAfterInteractions(() => {
const theme = themeState.theme
requestAnimationFrame(() => {
void InteractionManager.runAfterInteractions(() => {
const theme = themeState.theme
void Navigation.push(componentId, {
component: {
name: PLAY_DETAIL_SCREEN,
options: {
topBar: {
visible: false,
height: 0,
drawBehind: false,
},
statusBar: {
drawBehind: true,
visible: true,
style: getStatusBarStyle(theme.isDark),
backgroundColor: 'transparent',
},
// navigationBar: {
// // visible: false,
// backgroundColor: theme['c-content-background'],
// },
layout: {
componentBackgroundColor: theme['c-content-background'],
},
animations: {
push: {
sharedElementTransitions: [
{
fromId: NAV_SHEAR_NATIVE_IDS.playDetail_pic,
toId: NAV_SHEAR_NATIVE_IDS.playDetail_pic,
interpolation: { type: 'spring' },
},
],
elementTransitions: [
{
id: NAV_SHEAR_NATIVE_IDS.playDetail_header,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
void Navigation.push(componentId, {
component: {
name: PLAY_DETAIL_SCREEN,
options: {
topBar: {
visible: false,
height: 0,
drawBehind: false,
},
statusBar: {
drawBehind: true,
visible: true,
style: getStatusBarStyle(theme.isDark),
backgroundColor: 'transparent',
},
// navigationBar: {
// // visible: false,
// backgroundColor: theme['c-content-background'],
// },
layout: {
componentBackgroundColor: theme['c-content-background'],
},
animations: {
push: {
sharedElementTransitions: [
{
fromId: NAV_SHEAR_NATIVE_IDS.playDetail_pic,
toId: NAV_SHEAR_NATIVE_IDS.playDetail_pic,
interpolation: { type: 'spring' },
},
translationY: {
from: -32, // Animate translationY from 16dp to 0dp
duration: 300,
],
elementTransitions: [
{
id: NAV_SHEAR_NATIVE_IDS.playDetail_header,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
translationY: {
from: -32, // Animate translationY from 16dp to 0dp
duration: 300,
},
},
},
{
id: NAV_SHEAR_NATIVE_IDS.playDetail_pageIndicator,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
{
id: NAV_SHEAR_NATIVE_IDS.playDetail_pageIndicator,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
translationX: {
from: -32, // Animate translationY from 16dp to 0dp
duration: 300,
},
},
translationX: {
from: -32, // Animate translationY from 16dp to 0dp
duration: 300,
{
id: NAV_SHEAR_NATIVE_IDS.playDetail_player,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
translationY: {
from: 32, // Animate translationY from 16dp to 0dp
duration: 300,
},
},
},
{
id: NAV_SHEAR_NATIVE_IDS.playDetail_player,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
translationY: {
from: 32, // Animate translationY from 16dp to 0dp
duration: 300,
},
},
],
],
// content: {
// translationX: {
// from: windowSizeTools.getSize().width,
@ -193,70 +194,72 @@ export function pushPlayDetailScreen(componentId: string) {
// duration: 300,
// },
// },
},
pop: {
content: {
translationX: {
from: 0,
to: windowSizeTools.getSize().width,
duration: 300,
},
pop: {
content: {
translationX: {
from: 0,
to: windowSizeTools.getSize().width,
duration: 300,
},
},
},
},
},
},
},
})
})
})
}
export function pushSonglistDetailScreen(componentId: string, id: string) {
const theme = themeState.theme
void InteractionManager.runAfterInteractions(() => {
void Navigation.push(componentId, {
component: {
name: SONGLIST_DETAIL_SCREEN,
options: {
topBar: {
visible: false,
height: 0,
drawBehind: false,
},
statusBar: {
drawBehind: true,
visible: true,
style: getStatusBarStyle(theme.isDark),
backgroundColor: 'transparent',
},
// navigationBar: {
// // visible: false,
// backgroundColor: theme['c-content-background'],
// },
layout: {
componentBackgroundColor: theme['c-content-background'],
},
animations: {
push: {
sharedElementTransitions: [
{
fromId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_from_${id}`,
toId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_to_${id}`,
interpolation: { type: 'spring' },
},
],
elementTransitions: [
{
id: NAV_SHEAR_NATIVE_IDS.songlistDetail_title,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
requestAnimationFrame(() => {
void InteractionManager.runAfterInteractions(() => {
void Navigation.push(componentId, {
component: {
name: SONGLIST_DETAIL_SCREEN,
options: {
topBar: {
visible: false,
height: 0,
drawBehind: false,
},
statusBar: {
drawBehind: true,
visible: true,
style: getStatusBarStyle(theme.isDark),
backgroundColor: 'transparent',
},
// navigationBar: {
// // visible: false,
// backgroundColor: theme['c-content-background'],
// },
layout: {
componentBackgroundColor: theme['c-content-background'],
},
animations: {
push: {
sharedElementTransitions: [
{
fromId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_from_${id}`,
toId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_to_${id}`,
interpolation: { type: 'spring' },
},
translationX: {
from: 16, // Animate translationX from 16dp to 0dp
duration: 300,
],
elementTransitions: [
{
id: NAV_SHEAR_NATIVE_IDS.songlistDetail_title,
alpha: {
from: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
translationX: {
from: 16, // Animate translationX from 16dp to 0dp
duration: 300,
},
},
},
],
],
// content: {
// scaleX: {
// from: 1.2,
@ -274,28 +277,28 @@ export function pushSonglistDetailScreen(componentId: string, id: string) {
// duration: 200,
// },
// },
},
pop: {
sharedElementTransitions: [
{
fromId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_to_${id}`,
toId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_from_${id}`,
interpolation: { type: 'spring' },
},
],
elementTransitions: [
{
id: NAV_SHEAR_NATIVE_IDS.songlistDetail_title,
alpha: {
to: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
pop: {
sharedElementTransitions: [
{
fromId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_to_${id}`,
toId: `${NAV_SHEAR_NATIVE_IDS.songlistDetail_pic}_from_${id}`,
interpolation: { type: 'spring' },
},
translationX: {
to: 16, // Animate translationX from 16dp to 0dp
duration: 300,
],
elementTransitions: [
{
id: NAV_SHEAR_NATIVE_IDS.songlistDetail_title,
alpha: {
to: 0, // We don't declare 'to' value as that is the element's current alpha value, here we're essentially animating from 0 to 1
duration: 300,
},
translationX: {
to: 16, // Animate translationX from 16dp to 0dp
duration: 300,
},
},
},
],
],
// content: {
// alpha: {
// from: 1,
@ -303,10 +306,11 @@ export function pushSonglistDetailScreen(componentId: string, id: string) {
// duration: 200,
// },
// },
},
},
},
},
},
})
})
})
}
@ -343,53 +347,55 @@ export function pushCommentScreen(componentId: string) {
},
})
*/
void InteractionManager.runAfterInteractions(() => {
const theme = themeState.theme
requestAnimationFrame(() => {
void InteractionManager.runAfterInteractions(() => {
const theme = themeState.theme
void Navigation.push(componentId, {
component: {
name: COMMENT_SCREEN,
options: {
topBar: {
visible: false,
height: 0,
drawBehind: false,
},
statusBar: {
drawBehind: true,
visible: true,
style: getStatusBarStyle(theme.isDark),
backgroundColor: 'transparent',
},
// navigationBar: {
// // visible: false,
// backgroundColor: theme['c-content-background'],
// },
layout: {
componentBackgroundColor: theme['c-content-background'],
},
animations: {
push: {
content: {
translationX: {
from: windowSizeTools.getSize().width,
to: 0,
duration: 300,
void Navigation.push(componentId, {
component: {
name: COMMENT_SCREEN,
options: {
topBar: {
visible: false,
height: 0,
drawBehind: false,
},
statusBar: {
drawBehind: true,
visible: true,
style: getStatusBarStyle(theme.isDark),
backgroundColor: 'transparent',
},
// navigationBar: {
// // visible: false,
// backgroundColor: theme['c-content-background'],
// },
layout: {
componentBackgroundColor: theme['c-content-background'],
},
animations: {
push: {
content: {
translationX: {
from: windowSizeTools.getSize().width,
to: 0,
duration: 300,
},
},
},
},
pop: {
content: {
translationX: {
from: 0,
to: windowSizeTools.getSize().width,
duration: 300,
pop: {
content: {
translationX: {
from: 0,
to: windowSizeTools.getSize().width,
duration: 300,
},
},
},
},
},
},
},
})
})
})
}

View File

@ -10,6 +10,9 @@ import { removeUserApi, setUserApiAllowShowUpdateAlert } from '@/core/userApi'
import { BorderRadius } from '@/theme'
import CheckBox from '@/components/common/CheckBox'
import { Icon } from '@/components/common/Icon'
import settingState from '@/store/setting/state'
import apiSourceInfo from '@/utils/musicSdk/api-source-info'
import { setApiSource } from '@/core/apiSource'
const ListItem = ({ item, activeId, onRemove, onChangeAllowShowUpdateAlert }: {
@ -29,12 +32,12 @@ const ListItem = ({ item, activeId, onRemove, onChangeAllowShowUpdateAlert }: {
return (
<View style={{ ...styles.listItem, backgroundColor: activeId == item.id ? theme['c-primary-background-active'] : 'transparent' }}>
<View>
<Text size={13}>{item.name}</Text>
<View style={styles.listItemLeft}>
<Text size={14}>{item.name}</Text>
<Text size={12} color={theme['c-font-label']}>{item.description}</Text>
<CheckBox check={item.allowShowUpdateAlert} label={t('user_api_allow_show_update_alert')} onChange={changeAllowShowUpdateAlert} size={0.8} />
<CheckBox check={item.allowShowUpdateAlert} label={t('user_api_allow_show_update_alert')} onChange={changeAllowShowUpdateAlert} size={0.86} />
</View>
<View>
<View style={styles.listItemRight}>
<TouchableOpacity style={styles.btn} onPress={handleRemove}>
<Icon name="close" color={theme['c-button-font']} />
</TouchableOpacity>
@ -55,6 +58,8 @@ export interface UserApiEditModalType {
export default () => {
const userApiList = useUserApiList()
const apiSource = useSettingValue('common.apiSource')
const theme = useTheme()
const t = useI18n()
const handleRemove = useCallback(async(id: string, name: string) => {
const confirm = await confirmDialog({
@ -64,7 +69,12 @@ export default () => {
bgClose: false,
})
if (!confirm) return
void removeUserApi([id])
void removeUserApi([id]).finally(() => {
if (settingState.setting['common.apiSource'] == id) {
let backApi = apiSourceInfo.find(api => !api.disabled)
setApiSource(backApi?.id ?? '')
}
})
}, [])
const handleChangeAllowShowUpdateAlert = useCallback((id: string, enabled: boolean) => {
void setUserApiAllowShowUpdateAlert(id, enabled)
@ -74,8 +84,9 @@ export default () => {
<ScrollView style={styles.scrollView} keyboardShouldPersistTaps={'always'}>
<View onStartShouldSetResponder={() => true}>
{
userApiList.map((item) => {
return (
userApiList.length
? userApiList.map((item) => {
return (
<ListItem
key={item.id}
item={item}
@ -83,8 +94,9 @@ export default () => {
onRemove={handleRemove}
onChangeAllowShowUpdateAlert={handleChangeAllowShowUpdateAlert}
/>
)
})
)
})
: <Text style={styles.tipText} color={theme['c-font-label']}>{t('user_api_empty')}</Text>
}
</View>
</ScrollView>
@ -107,6 +119,15 @@ const styles = createStyle({
alignItems: 'center',
justifyContent: 'space-between',
},
listItemLeft: {
paddingRight: 10,
flex: 1,
gap: 2,
},
listItemRight: {
flex: 0,
// backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
// btns: {
// padding: 5,
// },
@ -114,6 +135,11 @@ const styles = createStyle({
padding: 10,
// backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
tipText: {
textAlign: 'center',
marginTop: 25,
marginBottom: 15,
},
})

View File

@ -1,13 +1,14 @@
import { useRef, useImperativeHandle, forwardRef, useState } from 'react'
import Text from '@/components/common/Text'
import { View, TouchableOpacity } from 'react-native'
import { createStyle, openUrl } from '@/utils/tools'
import { createStyle, openUrl, tipDialog } from '@/utils/tools'
import { useTheme } from '@/store/theme/hook'
import { useI18n } from '@/lang'
import Dialog, { type DialogType } from '@/components/common/Dialog'
import Button from '@/components/common/Button'
import List from './List'
import ScriptImportExport, { type ScriptImportExportType } from './ScriptImportExport'
import { state } from '@/store/userApi'
// interface UrlInputType {
// setText: (text: string) => void
@ -96,6 +97,13 @@ export default forwardRef<UserApiEditModalType, {}>((props, ref) => {
dialogRef.current?.setVisible(false)
}
const handleImport = () => {
if (state.list.length > 20) {
void tipDialog({
message: t('user_api_max_tip'),
btnText: t('ok'),
})
return
}
scriptImportExportRef.current?.import()
}
const openFAQPage = () => {
@ -108,7 +116,7 @@ export default forwardRef<UserApiEditModalType, {}>((props, ref) => {
<Dialog ref={dialogRef} bgHide={false}>
<View style={styles.content}>
{/* <UrlInput ref={inputRef} /> */}
<Text style={styles.title}>{t('user_api_title')}</Text>
<Text size={16} style={styles.title}>{t('user_api_title')}</Text>
<List />
<View style={styles.tips}>
<Text style={styles.tipsText} size={12}>
@ -169,10 +177,7 @@ const styles = createStyle({
},
btn: {
flex: 1,
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 10,
paddingRight: 10,
padding: 10,
alignItems: 'center',
borderRadius: 4,
marginRight: 15,

View File

@ -45,7 +45,7 @@ const RuleInput = forwardRef<RuleInputType, {}>((props, ref) => {
multiline
textAlignVertical="top"
placeholder={t('setting_dislike_list_input_tip')}
size={12}
size={13}
style={{ ...styles.input, height, backgroundColor: theme['c-primary-input-background'] }}
/>
</View>
@ -106,7 +106,7 @@ export default forwardRef<DislikeEditModalType, DislikeEditModalProps>(({ onSave
<Dialog height='80%' ref={dialogRef} bgHide={false}>
<View style={styles.content}>
<RuleInput ref={inputRef} />
<Text style={styles.inputTipText} size={12} color={theme['c-600']}>{t('setting_dislike_list_tips')}</Text>
<Text style={styles.inputTipText} size={13} color={theme['c-600']}>{t('setting_dislike_list_tips')}</Text>
</View>
<View style={styles.btns}>
<Button style={{ ...styles.btn, backgroundColor: theme['c-button-background'] }} onPress={handleCancel}>
@ -167,10 +167,7 @@ const styles = createStyle({
},
btn: {
flex: 1,
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 10,
paddingRight: 10,
padding: 10,
alignItems: 'center',
borderRadius: 4,
marginRight: 15,

View File

@ -175,13 +175,14 @@ export const handleReadFile = async<T = unknown>(path: string): Promise<T> => {
}
export const confirmDialog = async({
title = '',
message = '',
cancelButtonText = global.i18n.t('dialog_cancel'),
confirmButtonText = global.i18n.t('dialog_confirm'),
bgClose = true,
}) => {
return new Promise(resolve => {
Alert.alert('', message, [
return new Promise<boolean>(resolve => {
Alert.alert(title, message, [
{
text: cancelButtonText,
onPress() {
@ -203,6 +204,29 @@ export const confirmDialog = async({
})
}
export const tipDialog = async({
title = '',
message = '',
btnText = global.i18n.t('dialog_confirm'),
bgClose = true,
}) => {
return new Promise<void>(resolve => {
Alert.alert(title, message, [
{
text: btnText,
onPress() {
resolve()
},
},
], {
cancelable: bgClose,
onDismiss() {
resolve()
},
})
})
}
export const clipboardWriteText = (str: string) => {
Clipboard.setString(str)
}
@ -365,15 +389,11 @@ export const showImportTip = (type: string) => {
message = global.i18n.t('list_import_tip__unknown')
break
}
Alert.alert(
global.i18n.t('list_import_tip__failed'),
void tipDialog({
title: global.i18n.t('list_import_tip__failed'),
message,
[
{
text: global.i18n.t('ok'),
},
],
)
btnText: global.i18n.t('ok'),
})
}
@ -513,19 +533,14 @@ export const cheatTip = async() => {
const isRead = await getData<boolean>(storageDataPrefix.cheatTip)
if (isRead) return
return new Promise<void>((resolve) => {
Alert.alert(
'谨防被骗提示',
`1. 本项目无微信公众号之类的官方账号也未在小米、华为、vivo等应用商店发布应用商店内的“LX Music”、“洛雪音乐”相关的应用全部属于假冒应用谨防被骗。
return tipDialog({
title: '谨防被骗提示',
message: `1. 本项目无微信公众号之类的官方账号也未在小米、华为、vivo等应用商店发布应用商店内的“LX Music”、“洛雪音乐”相关的应用全部属于假冒应用谨防被骗。
2. 广使使广
3. GitHub -`,
[{
text: '我知道了 (Close)',
onPress: () => {
void saveData(storageDataPrefix.cheatTip, true)
resolve()
},
}],
)
btnText: '我知道了 (Close)',
bgClose: false,
}).then(() => {
void saveData(storageDataPrefix.cheatTip, true)
})
}