mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-05-23 22:37:41 +08:00
完善自定义源
This commit is contained in:
parent
35ecd2022b
commit
1a97295c7b
@ -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',
|
||||
|
50
src/app.ts
50
src/app.ts
@ -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()
|
||||
})
|
||||
})
|
||||
|
@ -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}>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
122
src/core/init/userApi/request.js
Normal file
122
src/core/init/userApi/request.js
Normal 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()
|
||||
},
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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": "It’s 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",
|
||||
|
@ -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": "重试",
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user