mirror of
https://github.com/ikun0014/lx-music-mobile.git
synced 2025-07-04 08:52:09 +08:00
完善同步功能
This commit is contained in:
parent
1fe5656eda
commit
5933825f9b
@ -9,6 +9,8 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:usesCleartextTraffic="true"
|
||||
|
@ -1,8 +1,11 @@
|
||||
package com.lxmusicmobile.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
@ -128,5 +131,51 @@ public class UtilsModule extends ReactContextBaseJavaModule {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the device's WiFi interface IP address
|
||||
@return device's WiFi IP if connected to WiFi, else '0.0.0.0'
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getWIFIIPV4Address(final Promise promise) throws Exception {
|
||||
// https://github.com/pusherman/react-native-network-info/blob/master/android/src/main/java/com/pusherman/networkinfo/RNNetworkInfo.java
|
||||
WifiManager wifi = (WifiManager) reactContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
WifiInfo info = wifi.getConnectionInfo();
|
||||
int ipAddress = info.getIpAddress();
|
||||
String stringip = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
|
||||
(ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
|
||||
promise.resolve(stringip);
|
||||
}catch (Exception e) {
|
||||
promise.resolve(null);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/26117646
|
||||
@ReactMethod
|
||||
public void getDeviceName(final Promise promise) {
|
||||
String manufacturer = Build.MANUFACTURER;
|
||||
String model = Build.MODEL;
|
||||
if (model.startsWith(manufacturer)) {
|
||||
promise.resolve(capitalize(model));
|
||||
} else {
|
||||
promise.resolve(capitalize(manufacturer) + " " + model);
|
||||
}
|
||||
}
|
||||
private String capitalize(String s) {
|
||||
if (s == null || s.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
char first = s.charAt(0);
|
||||
if (Character.isUpperCase(first)) {
|
||||
return s;
|
||||
} else {
|
||||
return Character.toUpperCase(first) + s.substring(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-export-namespace-from',
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
|
2
index.js
2
index.js
@ -20,6 +20,7 @@ import { init as initLyric, toggleTranslation } from '@/plugins/lyric'
|
||||
import { init as initI18n, supportedLngs } from '@/plugins/i18n'
|
||||
import { deviceLanguage, getPlayInfo, toast } from '@/utils/tools'
|
||||
import { LIST_ID_PLAY_TEMP } from '@/config/constant'
|
||||
import { connect } from '@/plugins/sync'
|
||||
|
||||
console.log('starting app...')
|
||||
|
||||
@ -41,6 +42,7 @@ const init = () => {
|
||||
]).then(() => {
|
||||
let setting = store.getState().common.setting
|
||||
toggleTranslation(setting.player.isShowTranslation)
|
||||
if (setting.sync.enable) connect(setting.sync.host, setting.sync.port)
|
||||
|
||||
let lang = setting.langId
|
||||
let needSetLang = false
|
||||
|
169
package-lock.json
generated
169
package-lock.json
generated
@ -777,6 +777,24 @@
|
||||
"@babel/plugin-syntax-export-default-from": "^7.12.13"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-export-namespace-from": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz",
|
||||
"integrity": "sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5",
|
||||
"@babel/plugin-syntax-export-namespace-from": "^7.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz",
|
||||
"integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": {
|
||||
"version": "7.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz",
|
||||
@ -859,6 +877,15 @@
|
||||
"@babel/helper-plugin-utils": "^7.12.13"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-export-namespace-from": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
|
||||
"integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-flow": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.13.tgz",
|
||||
@ -2667,6 +2694,21 @@
|
||||
"@types/responselike": "*"
|
||||
}
|
||||
},
|
||||
"@types/component-emitter": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz",
|
||||
"integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg=="
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.6.tgz",
|
||||
@ -3827,11 +3869,21 @@
|
||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
@ -4465,6 +4517,11 @@
|
||||
"safe-buffer": "~5.1.1"
|
||||
}
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||
},
|
||||
"copy-descriptor": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||
@ -4496,6 +4553,15 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"requires": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
|
||||
@ -5169,6 +5235,48 @@
|
||||
"resolved": "https://registry.npmjs.org/endian-reader/-/endian-reader-0.3.0.tgz",
|
||||
"integrity": "sha1-hOykNrgK7Q0GOcRykTOLky7+UKA="
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.1.1.tgz",
|
||||
"integrity": "sha512-aMWot7H5aC8L4/T8qMYbLdvKlZOdJTH54FxfdFunTGvhMx1BHkJOntWArsVfgAZVwAO9LC2sryPWRcEeUzCe5w==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~4.0.0",
|
||||
"ws": "~7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz",
|
||||
"integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4"
|
||||
}
|
||||
},
|
||||
"enquirer": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
|
||||
@ -13430,6 +13538,67 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz",
|
||||
"integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io": "~5.1.1",
|
||||
"socket.io-adapter": "~2.3.1",
|
||||
"socket.io-parser": "~4.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.1.tgz",
|
||||
"integrity": "sha512-8cVkRxI8Nt2wadkY6u60Y4rpW3ejA1rxgcK2JuyIhmF+RMNpTy1QRtkHIDUOf3B4HlQwakMsWbKftMv/71VMmw=="
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
|
||||
"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
|
@ -69,12 +69,14 @@
|
||||
"redux-subscriber": "^1.1.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"socket.io": "^4.1.2",
|
||||
"stream-browserify": "^1.0.0",
|
||||
"url": "~0.10.1",
|
||||
"util": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.8",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.14.5",
|
||||
"@babel/runtime": "^7.14.8",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
|
@ -109,8 +109,8 @@ export default memo(({ visible, hideModal, musicInfo, listId, isMove = false })
|
||||
const index = list.list.indexOf(musicInfo)
|
||||
if (index > -1) {
|
||||
removeMusicFromList({
|
||||
id: list.id,
|
||||
index,
|
||||
listId: list.id,
|
||||
id: musicInfo.songmid,
|
||||
})
|
||||
toast(t('list_edit_action_tip_remove_success'))
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useRef, useImperativeHandle, forwardRef, useCallback, useEffect, useState } from 'react'
|
||||
import { TextInput, StyleSheet, View, TouchableOpacity, Animated } from 'react-native'
|
||||
import React, { useRef, useImperativeHandle, forwardRef, useCallback } from 'react'
|
||||
import { TextInput, StyleSheet, View, TouchableOpacity } from 'react-native'
|
||||
import Icon from './Icon'
|
||||
import { useGetter } from '@/store'
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
// const { isMac } = require('./utils')
|
||||
|
||||
const defaultSetting = {
|
||||
version: '1.6',
|
||||
version: '1.7',
|
||||
player: {
|
||||
togglePlayMethod: 'listLoop',
|
||||
highQuality: false,
|
||||
@ -52,6 +52,9 @@ const defaultSetting = {
|
||||
isShowHistorySearch: false,
|
||||
isFocusSearchBox: false,
|
||||
},
|
||||
sync: {
|
||||
enable: false,
|
||||
},
|
||||
// network: {
|
||||
// proxy: {
|
||||
// enable: false,
|
||||
|
@ -17,3 +17,7 @@ global.isScreenKeepAwake = false
|
||||
|
||||
// 是否播放完后退出应用
|
||||
global.isPlayedExit = false
|
||||
|
||||
|
||||
global.syncKeyInfo = {}
|
||||
global.isSyncEnableing = false
|
||||
|
@ -17,6 +17,8 @@ export const storageDataPrefix = {
|
||||
lyric: '@lyric__',
|
||||
musicUrl: '@music_url__',
|
||||
playInfo: '@play_info',
|
||||
syncAuthKey: '@sync_auth_key',
|
||||
syncHost: '@sync_host',
|
||||
}
|
||||
|
||||
export const ITEM_HEIGHT = 60
|
||||
|
@ -121,6 +121,18 @@
|
||||
"setting_play_quality": "Play 320K quality songs first (if supported)",
|
||||
"setting_play_save_play_time": "Remember playback progress",
|
||||
"setting_play_show_translation": "Show lyrics translation",
|
||||
"setting_sync": "Synchronization [It is recommended to back up the playlist before using it for the first time]",
|
||||
"setting_sync_address": "Local IP address: {{address}}",
|
||||
"setting_sync_code_fail": "Invalid connection code",
|
||||
"setting_sync_code_input_tip": "Please enter the connection code",
|
||||
"setting_sync_code_label": "You need to enter the connection code for the first connection",
|
||||
"setting_sync_enbale": "Enable sync",
|
||||
"setting_sync_host_label": "Synchronization service IP address",
|
||||
"setting_sync_host_tip": "Please enter the synchronization service IP address",
|
||||
"setting_sync_port_label": "Synchronization service port number",
|
||||
"setting_sync_port_tip": "Please enter the synchronization service port number",
|
||||
"setting_sync_status": "Status: {{status}}",
|
||||
"setting_sync_status_enabled": "Connected",
|
||||
"setting_version": "Software Update",
|
||||
"setting_version_show_ver_modal": "Open the update window 🚀",
|
||||
"singer": "Artist: {{name}}",
|
||||
@ -150,6 +162,7 @@
|
||||
"storage_permission_tip_disagree": "User Disagree",
|
||||
"storage_permission_tip_disagree_ask_again": "This feature cannot be used because you have permanently denied LX access to the phone storage.\nIf you want to continue, you need to go to System Permission Management Set Luo Xue’s storage permission to allow.",
|
||||
"storage_permission_tip_request": "To use this function, you need to allow LX to access the phone storage. Do you agree and continue?",
|
||||
"sync_status_disabled": "Not connected",
|
||||
"theme_blue": "Blue",
|
||||
"theme_blue2": "Purple Blue",
|
||||
"theme_green": "Green",
|
||||
|
@ -20,7 +20,7 @@
|
||||
"disagree": "我就不",
|
||||
"disagree_tip": "那算了... 🙄",
|
||||
"input_error": "不要乱输好吧😡",
|
||||
"list_add_btn_title": "把该歌曲添加到 {name}",
|
||||
"list_add_btn_title": "把该歌曲添加到 {{name}}",
|
||||
"list_add_title_first_add": "添加",
|
||||
"list_add_title_first_move": "移动",
|
||||
"list_add_title_last": "到...",
|
||||
@ -122,6 +122,18 @@
|
||||
"setting_play_quality": "优先播放320K品质的歌曲(如果支持)",
|
||||
"setting_play_save_play_time": "记住播放进度",
|
||||
"setting_play_show_translation": "显示歌词翻译",
|
||||
"setting_sync": "同步 [首次使用前建议先备份一次歌单]",
|
||||
"setting_sync_address": "本机IP地址:{{address}}",
|
||||
"setting_sync_code_fail": "连接码无效",
|
||||
"setting_sync_code_input_tip": "请输入连接码",
|
||||
"setting_sync_code_label": "首次连接需要输入连接码",
|
||||
"setting_sync_enbale": "启用同步",
|
||||
"setting_sync_host_label": "同步服务IP地址",
|
||||
"setting_sync_host_tip": "请输入同步服务IP地址",
|
||||
"setting_sync_port_label": "同步服务端口号",
|
||||
"setting_sync_port_tip": "请输入同步服务端口号",
|
||||
"setting_sync_status": "状态:{{status}}",
|
||||
"setting_sync_status_enabled": "已连接",
|
||||
"setting_version": "软件更新",
|
||||
"setting_version_show_ver_modal": "打开更新窗口 🚀",
|
||||
"singer": "艺术家:{{name}}",
|
||||
@ -151,6 +163,7 @@
|
||||
"storage_permission_tip_disagree": "你个骗纸,刚刚问你,你都说同意的,最后又拒绝,哼 🥺",
|
||||
"storage_permission_tip_disagree_ask_again": "此功能无法使用,因为你已经永久拒绝洛雪访问手机存储😫。\n若想继续,你需要去👉系统权限管理👈将洛雪的存储权限设置为允许",
|
||||
"storage_permission_tip_request": "使用此功能需要允许洛雪访问手机存储,是否同意并继续?",
|
||||
"sync_status_disabled": "未连接",
|
||||
"theme_blue": "蓝田生玉",
|
||||
"theme_blue2": "清热版蓝",
|
||||
"theme_green": "绿意盎然",
|
||||
|
71
src/plugins/sync/client/auth.js
Normal file
71
src/plugins/sync/client/auth.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { getSyncAuthKey, setSyncAuthKey } from '@/utils/tools'
|
||||
import { request, aesEncrypt, aesDecrypt } from './utils'
|
||||
import { getDeviceName } from '@/utils/utils'
|
||||
import { SYNC_CODE } from './config'
|
||||
|
||||
|
||||
const hello = (host, port) => request(`http://${host}:${port}/hello`)
|
||||
.then(text => text == SYNC_CODE.helloMsg)
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
return false
|
||||
})
|
||||
|
||||
const getServerId = (host, port) => request(`http://${host}:${port}/id`)
|
||||
.then(text => {
|
||||
if (!text.startsWith(SYNC_CODE.idPrefix)) return ''
|
||||
return text.replace(SYNC_CODE.idPrefix, '')
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
return false
|
||||
})
|
||||
|
||||
const codeAuth = async(host, port, serverId, authCode) => {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
const msg = aesEncrypt(SYNC_CODE.authMsg + await getDeviceName(), key, iv)
|
||||
return request(`http://${host}:${port}/ah`, { headers: { m: msg } }).then(text => {
|
||||
// console.log(text)
|
||||
let msg
|
||||
try {
|
||||
msg = aesDecrypt(text, key, iv)
|
||||
} catch (err) {
|
||||
throw new Error(SYNC_CODE.authFailed)
|
||||
}
|
||||
if (!msg) return Promise.reject(new Error(SYNC_CODE.authFailed))
|
||||
const info = JSON.parse(msg)
|
||||
setSyncAuthKey(serverId, info)
|
||||
return info
|
||||
})
|
||||
}
|
||||
|
||||
const keyAuth = async(host, port, keyInfo) => {
|
||||
const msg = aesEncrypt(SYNC_CODE.authMsg + await getDeviceName(), keyInfo.key, keyInfo.iv)
|
||||
return request(`http://${host}:${port}/ah`, { headers: { i: keyInfo.clientId, m: msg } }).then(text => {
|
||||
let msg
|
||||
try {
|
||||
msg = aesDecrypt(text, keyInfo.key, keyInfo.iv)
|
||||
} catch (err) {
|
||||
throw new Error(SYNC_CODE.authFailed)
|
||||
}
|
||||
if (msg != SYNC_CODE.helloMsg) return Promise.reject(new Error(SYNC_CODE.authFailed))
|
||||
})
|
||||
}
|
||||
|
||||
const auth = async(host, port, serverId, authCode) => {
|
||||
if (authCode) return codeAuth(host, port, serverId, authCode)
|
||||
const keyInfo = await getSyncAuthKey(serverId)
|
||||
if (!keyInfo) throw new Error(SYNC_CODE.missingAuthCode)
|
||||
await keyAuth(host, port, keyInfo)
|
||||
return keyInfo
|
||||
}
|
||||
|
||||
export default async(host, port, authCode) => {
|
||||
console.log('connect: ', host, port, authCode)
|
||||
if (!await hello(host, port)) throw new Error(SYNC_CODE.connectServiceFailed)
|
||||
const serverId = await getServerId(host, port)
|
||||
if (!serverId) throw new Error(SYNC_CODE.getServiceIdFailed)
|
||||
return auth(host, port, serverId, authCode)
|
||||
}
|
93
src/plugins/sync/client/client.js
Normal file
93
src/plugins/sync/client/client.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { io } from 'socket.io/client-dist/socket.io'
|
||||
import { aesEncrypt } from './utils'
|
||||
import * as modules from '../modules'
|
||||
import { action as commonAction } from '@/store/modules/common'
|
||||
import { getStore } from '@/store'
|
||||
import syncList from './syncList'
|
||||
|
||||
const handleConnection = (socket) => {
|
||||
for (const module of Object.values(modules)) {
|
||||
module.registerListHandler(socket)
|
||||
}
|
||||
}
|
||||
|
||||
let socket
|
||||
export const connect = (host, port, keyInfo) => {
|
||||
socket = io(`ws://${host}:${port}`, {
|
||||
path: '/sync',
|
||||
reconnectionAttempts: 5,
|
||||
transports: ['websocket'],
|
||||
query: {
|
||||
i: keyInfo.clientId,
|
||||
t: aesEncrypt('lx-music connect', keyInfo.key, keyInfo.iv),
|
||||
},
|
||||
})
|
||||
|
||||
socket.on('connect', async() => {
|
||||
console.log('connect')
|
||||
const store = getStore()
|
||||
global.syncKeyInfo = keyInfo
|
||||
try {
|
||||
await syncList(socket)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
handleConnection(socket)
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: true,
|
||||
message: '',
|
||||
}))
|
||||
})
|
||||
socket.on('connect_error', (err) => {
|
||||
console.log(err.message)
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: false,
|
||||
message: err.message,
|
||||
}))
|
||||
// if (err.message === 'invalid credentials') {
|
||||
// socket.auth.token = 'efgh'
|
||||
// socket.connect()
|
||||
// }
|
||||
})
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log('disconnect', reason)
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: false,
|
||||
message: reason,
|
||||
}))
|
||||
// if (reason === 'io server disconnect') {
|
||||
// // the disconnection was initiated by the server, you need to reconnect manually
|
||||
// socket.connect()
|
||||
// }
|
||||
// else the socket will automatically try to reconnect
|
||||
})
|
||||
|
||||
// ws.onopen = () => {
|
||||
// // connection opened
|
||||
// ws.send('something') // send a message
|
||||
// }
|
||||
|
||||
// ws.onmessage = (e) => {
|
||||
// // a message was received
|
||||
// console.log(e.data)
|
||||
// }
|
||||
|
||||
// ws.onerror = (e) => {
|
||||
// // an error occurred
|
||||
// console.log(e.message)
|
||||
// }
|
||||
|
||||
// ws.onclose = (e) => {
|
||||
// // connection closed
|
||||
// console.log(e.code, e.reason)
|
||||
// }
|
||||
}
|
||||
|
||||
export const disconnect = async() => {
|
||||
if (!socket) return
|
||||
await socket.close()
|
||||
socket = null
|
||||
}
|
10
src/plugins/sync/client/config.js
Normal file
10
src/plugins/sync/client/config.js
Normal file
@ -0,0 +1,10 @@
|
||||
export const SYNC_CODE = {
|
||||
helloMsg: 'Hello~::^-^::',
|
||||
idPrefix: 'OjppZDo6',
|
||||
authMsg: 'lx-music auth::',
|
||||
authFailed: 'Auth failed',
|
||||
missingAuthCode: 'Missing auth code',
|
||||
getServiceIdFailed: 'Get service id failed',
|
||||
connectServiceFailed: 'Connect service failed',
|
||||
connecting: 'Connecting...',
|
||||
}
|
59
src/plugins/sync/client/index.js
Normal file
59
src/plugins/sync/client/index.js
Normal file
@ -0,0 +1,59 @@
|
||||
import handleAuth from './auth'
|
||||
import { connect as socketConnect, disconnect as socketDisconnect } from './client'
|
||||
import { getSyncHost } from '@/utils/tools'
|
||||
import { action as commonAction } from '@/store/modules/common'
|
||||
import { getStore } from '@/store'
|
||||
import { SYNC_CODE } from './config'
|
||||
|
||||
const handleConnect = async authCode => {
|
||||
const hostInfo = await getSyncHost()
|
||||
if (!hostInfo || !hostInfo.host || !hostInfo.port) throw new Error('Unknown service address')
|
||||
await disconnect(false)
|
||||
const keyInfo = await handleAuth(hostInfo.host, hostInfo.port, authCode)
|
||||
await socketConnect(hostInfo.host, hostInfo.port, keyInfo)
|
||||
}
|
||||
const handleDisconnect = async() => {
|
||||
await socketDisconnect()
|
||||
}
|
||||
|
||||
const connect = authCode => {
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: false,
|
||||
message: SYNC_CODE.connecting,
|
||||
}))
|
||||
return handleConnect(authCode).then(() => {
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: true,
|
||||
message: '',
|
||||
}))
|
||||
}).catch(err => {
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: false,
|
||||
message: err.message,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
const disconnect = (isResetStatus = true) => handleDisconnect().then(() => {
|
||||
if (isResetStatus) {
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: false,
|
||||
message: '',
|
||||
}))
|
||||
}
|
||||
}).catch(err => {
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
message: err.message,
|
||||
}))
|
||||
})
|
||||
|
||||
export {
|
||||
connect,
|
||||
disconnect,
|
||||
SYNC_CODE,
|
||||
}
|
80
src/plugins/sync/client/syncList.js
Normal file
80
src/plugins/sync/client/syncList.js
Normal file
@ -0,0 +1,80 @@
|
||||
import { getStore } from '@/store'
|
||||
import { action as commonAction } from '@/store/modules/common'
|
||||
import { action as listAction } from '@/store/modules/list'
|
||||
|
||||
import { decryptMsg, encryptMsg } from './utils'
|
||||
let socket
|
||||
let syncAction
|
||||
|
||||
const wait = () => new Promise((resolve, reject) => {
|
||||
syncAction = [resolve, reject]
|
||||
})
|
||||
|
||||
const sendListData = type => {
|
||||
const store = getStore()
|
||||
const state = store.getState()
|
||||
let listData
|
||||
switch (type) {
|
||||
case 'all':
|
||||
listData = {
|
||||
defaultList: state.list.defaultList,
|
||||
loveList: state.list.loveList,
|
||||
userList: state.list.userList,
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
// console.log('sendListData')
|
||||
socket.emit('list:sync', encryptMsg(JSON.stringify({
|
||||
action: 'getData',
|
||||
data: listData,
|
||||
})))
|
||||
// console.log('sendListData', 'encryptMsg')
|
||||
}
|
||||
|
||||
const saveList = ({ defaultList, loveList, userList }) => {
|
||||
const store = getStore()
|
||||
store.dispatch(listAction.setSyncList({ defaultList, loveList, userList }))
|
||||
}
|
||||
|
||||
const handleListSync = enMsg => {
|
||||
// console.log('handleListSync', enMsg.length)
|
||||
const { action, data } = JSON.parse(decryptMsg(enMsg))
|
||||
// console.log('handleListSync', action)
|
||||
switch (action) {
|
||||
case 'getData':
|
||||
sendListData(data)
|
||||
break
|
||||
case 'setData':
|
||||
saveList(data)
|
||||
break
|
||||
case 'finished':
|
||||
if (!syncAction) return
|
||||
syncAction[0]()
|
||||
syncAction = null
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const handleDisconnect = err => {
|
||||
if (!syncAction) return
|
||||
syncAction[1](err.message ? err : new Error(err))
|
||||
syncAction = null
|
||||
}
|
||||
|
||||
export default async _socket => {
|
||||
socket = _socket
|
||||
socket.on('list:sync', handleListSync)
|
||||
socket.on('connect_error', handleDisconnect)
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
const store = getStore()
|
||||
store.dispatch(commonAction.setSyncStatus({
|
||||
status: false,
|
||||
message: 'Syncing...',
|
||||
}))
|
||||
await wait()
|
||||
}
|
47
src/plugins/sync/client/utils.js
Normal file
47
src/plugins/sync/client/utils.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { createCipheriv, createDecipheriv } from 'crypto'
|
||||
import BackgroundTimer from 'react-native-background-timer'
|
||||
|
||||
export const request = (url, { timeout = 10000, ...options } = {}) => {
|
||||
const controller = new global.AbortController()
|
||||
const id = BackgroundTimer.setTimeout(() => controller.abort(), timeout)
|
||||
return global.fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal,
|
||||
}).then(response => {
|
||||
BackgroundTimer.clearTimeout(id)
|
||||
return response.text()
|
||||
}).catch(err => {
|
||||
// console.log(err, err.code, err.message)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
export const aesEncrypt = (text, key, iv) => {
|
||||
const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([cipher.update(Buffer.from(text)), cipher.final()]).toString('base64')
|
||||
}
|
||||
|
||||
export const aesDecrypt = (text, key, iv) => {
|
||||
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
export const encryptMsg = msg => {
|
||||
return msg
|
||||
// const keyInfo = global.syncKeyInfo
|
||||
// if (!keyInfo) return ''
|
||||
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
export const decryptMsg = enMsg => {
|
||||
return enMsg
|
||||
// const keyInfo = global.syncKeyInfo
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
2
src/plugins/sync/index.js
Normal file
2
src/plugins/sync/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './modules'
|
||||
export { connect, disconnect, SYNC_CODE } from './client'
|
1
src/plugins/sync/modules/index.js
Normal file
1
src/plugins/sync/modules/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * as list from './list'
|
18
src/plugins/sync/modules/list/index.js
Normal file
18
src/plugins/sync/modules/list/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { register as registerOn, unregister as unregisterOn } from './on'
|
||||
import {
|
||||
register as registerSend,
|
||||
unregister as unregisterSend,
|
||||
} from './send'
|
||||
|
||||
export const registerListHandler = _socket => {
|
||||
unregisterListHandler()
|
||||
registerOn(_socket)
|
||||
registerSend(_socket)
|
||||
}
|
||||
|
||||
export const unregisterListHandler = () => {
|
||||
unregisterOn()
|
||||
unregisterSend()
|
||||
}
|
||||
|
||||
export * from './send'
|
96
src/plugins/sync/modules/list/on.js
Normal file
96
src/plugins/sync/modules/list/on.js
Normal file
@ -0,0 +1,96 @@
|
||||
import { getStore } from '@/store'
|
||||
import { decryptMsg } from '../../client/utils'
|
||||
import {
|
||||
setList,
|
||||
listAdd,
|
||||
listMove,
|
||||
listAddMultiple,
|
||||
listMoveMultiple,
|
||||
listRemove,
|
||||
listRemoveMultiple,
|
||||
listClear,
|
||||
updateMusicInfo,
|
||||
createUserList,
|
||||
removeUserList,
|
||||
setUserListName,
|
||||
setMusicPosition,
|
||||
// moveupUserList,
|
||||
// movedownUserList,
|
||||
// setUserListPosition,
|
||||
} from '@/store/modules/list/action'
|
||||
|
||||
const store = getStore()
|
||||
|
||||
let socket
|
||||
|
||||
const handleListAction = enMsg => {
|
||||
const { action, data } = JSON.parse(decryptMsg(enMsg))
|
||||
if (typeof data == 'object') data.isSync = true
|
||||
console.log(action)
|
||||
|
||||
switch (action) {
|
||||
// case 'init_list':
|
||||
// store.dispatch(initList(data))
|
||||
// break
|
||||
case 'set_list':
|
||||
store.dispatch(setList(data))
|
||||
break
|
||||
case 'list_add':
|
||||
store.dispatch(listAdd(data))
|
||||
break
|
||||
case 'list_move':
|
||||
store.dispatch(listMove(data))
|
||||
break
|
||||
case 'list_add_multiple':
|
||||
store.dispatch(listAddMultiple(data))
|
||||
break
|
||||
case 'list_move_multiple':
|
||||
store.dispatch(listMoveMultiple(data))
|
||||
break
|
||||
case 'list_remove':
|
||||
store.dispatch(listRemove(data))
|
||||
break
|
||||
case 'list_remove_multiple':
|
||||
store.dispatch(listRemoveMultiple(data))
|
||||
break
|
||||
case 'list_clear':
|
||||
store.dispatch(listClear(data))
|
||||
break
|
||||
case 'update_music_info':
|
||||
store.dispatch(updateMusicInfo(data))
|
||||
break
|
||||
case 'create_user_list':
|
||||
store.dispatch(createUserList(data))
|
||||
break
|
||||
case 'remove_user_list':
|
||||
store.dispatch(removeUserList(data))
|
||||
break
|
||||
case 'set_user_list_name':
|
||||
store.dispatch(setUserListName(data))
|
||||
break
|
||||
case 'set_music_position':
|
||||
store.dispatch(setMusicPosition(data))
|
||||
break
|
||||
// case 'moveup_user_list':
|
||||
// store.dispatch(moveupUserList(data))
|
||||
// break
|
||||
// case 'movedown_user_list':
|
||||
// store.dispatch(movedownUserList(data))
|
||||
// break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export const register = _socket => {
|
||||
unregister()
|
||||
socket = _socket
|
||||
socket.on('list:action', handleListAction)
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
|
||||
export const unregister = () => {
|
||||
if (!socket) return
|
||||
socket.off('list:action', handleListAction)
|
||||
socket = null
|
||||
}
|
16
src/plugins/sync/modules/list/send.js
Normal file
16
src/plugins/sync/modules/list/send.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { encryptMsg } from '../../client/utils'
|
||||
|
||||
let socket
|
||||
|
||||
export const sendListAction = (action, data) => {
|
||||
if (!socket) return
|
||||
socket.emit('list:action', encryptMsg(JSON.stringify({ action, data })))
|
||||
}
|
||||
|
||||
export const register = _socket => {
|
||||
socket = _socket
|
||||
}
|
||||
|
||||
export const unregister = () => {
|
||||
socket = null
|
||||
}
|
@ -72,9 +72,8 @@ const List = memo(({ setVisiblePanel, currentList, activeListIdRef, handleCancel
|
||||
}, [setPrevSelectListId, setVisiblePanel])
|
||||
|
||||
const handleRemoveList = useCallback(id => {
|
||||
if (id == activeListIdRef.current) setPrevSelectListId(userList[0].id)
|
||||
removeUserList(id)
|
||||
}, [activeListIdRef, userList, removeUserList, setPrevSelectListId])
|
||||
removeUserList({ id })
|
||||
}, [removeUserList])
|
||||
|
||||
|
||||
const hideMenu = useCallback(() => {
|
||||
|
@ -212,10 +212,10 @@ const List = () => {
|
||||
break
|
||||
case 'remove':
|
||||
if (selectedListRef.current.length) {
|
||||
removeListMultiItem({ id: activeListIdRef.current, list: selectedListRef.current })
|
||||
removeListMultiItem({ listId: activeListIdRef.current, ids: selectedListRef.current.map(s => s.songmid) })
|
||||
handleCancelMultiSelect()
|
||||
} else {
|
||||
removeListItem({ id: activeListIdRef.current, index: selectedDataRef.current.index })
|
||||
removeListItem({ listId: activeListIdRef.current, id: selectedDataRef.current.data.songmid })
|
||||
}
|
||||
break
|
||||
default:
|
||||
|
215
src/screens/Home/Setting/Sync/IsEnable.js
Normal file
215
src/screens/Home/Setting/Sync/IsEnable.js
Normal file
@ -0,0 +1,215 @@
|
||||
import React, { memo, useCallback, useState, useEffect, useRef, useMemo } from 'react'
|
||||
import { View, Text, StyleSheet } from 'react-native'
|
||||
|
||||
import { useGetter, useDispatch } from '@/store'
|
||||
|
||||
import CheckBoxItem from '../components/CheckBoxItem'
|
||||
import ConfirmAlert from '@/components/common/ConfirmAlert'
|
||||
import Input from '@/components/common/Input'
|
||||
import { useTranslation } from '@/plugins/i18n'
|
||||
import { connect, disconnect, SYNC_CODE } from '@/plugins/sync'
|
||||
import { getSyncHost, setSyncHost, toast } from '@/utils/tools'
|
||||
import InputItem from '../components/InputItem'
|
||||
import { getWIFIIPV4Address } from '@/utils/utils'
|
||||
|
||||
const addressRxp = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
|
||||
const portRxp = /(\d+)/
|
||||
|
||||
const HostInput = memo(({ setHost, host, disabled }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const hostAddress = useMemo(() => {
|
||||
return addressRxp.test(host) ? RegExp.$1 : ''
|
||||
}, [host])
|
||||
|
||||
const setHostAddress = useCallback((value, callback) => {
|
||||
let hostAddress = addressRxp.test(value) ? RegExp.$1 : ''
|
||||
callback(hostAddress)
|
||||
if (host == hostAddress) return
|
||||
setHost(hostAddress)
|
||||
}, [host, setHost])
|
||||
|
||||
return (
|
||||
<InputItem
|
||||
editable={!disabled}
|
||||
value={hostAddress}
|
||||
label={t('setting_sync_host_label')}
|
||||
onChange={setHostAddress}
|
||||
keyboardType="number-pad"
|
||||
placeholder={t('setting_sync_host_tip')} />
|
||||
)
|
||||
})
|
||||
|
||||
const PortInput = memo(({ setPort, port, disabled }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const portNum = useMemo(() => {
|
||||
return portRxp.test(port) ? RegExp.$1 : ''
|
||||
}, [port])
|
||||
|
||||
const setPortAddress = useCallback((value, callback) => {
|
||||
let portNum = portRxp.test(value) ? RegExp.$1 : ''
|
||||
callback(portNum)
|
||||
if (port == portNum) return
|
||||
setPort(portNum)
|
||||
}, [port, setPort])
|
||||
|
||||
return (
|
||||
<InputItem
|
||||
editable={!disabled}
|
||||
value={portNum}
|
||||
label={t('setting_sync_port_label')}
|
||||
onChange={setPortAddress}
|
||||
keyboardType="number-pad"
|
||||
placeholder={t('setting_sync_port_tip')} />
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
export default memo(() => {
|
||||
const { t } = useTranslation()
|
||||
const setIsEnableSync = useDispatch('common', 'setIsEnableSync')
|
||||
const syncStatus = useGetter('common', 'syncStatus')
|
||||
const isEnableSync = useGetter('common', 'isEnableSync')
|
||||
const [isWaiting, setIsWaiting] = useState(global.isSyncEnableing)
|
||||
const [hostInfo, setHostInfo] = useState({ host: '', port: '' })
|
||||
const isUnmountedRef = useRef(true)
|
||||
const theme = useGetter('common', 'theme')
|
||||
const [address, setAddress] = useState('')
|
||||
const [visibleCodeModal, setVisibleCodeModal] = useState(false)
|
||||
const [authCode, setAuthCode] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
isUnmountedRef.current = false
|
||||
getSyncHost().then(hostInfo => {
|
||||
if (isUnmountedRef.current) return
|
||||
console.log(hostInfo)
|
||||
setHostInfo(hostInfo)
|
||||
})
|
||||
getWIFIIPV4Address().then(address => {
|
||||
if (isUnmountedRef.current) return
|
||||
setAddress(address)
|
||||
})
|
||||
|
||||
return () => {
|
||||
isUnmountedRef.current = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
switch (syncStatus.message) {
|
||||
case SYNC_CODE.authFailed:
|
||||
toast(t('setting_sync_code_fail'))
|
||||
case SYNC_CODE.missingAuthCode:
|
||||
setVisibleCodeModal(true)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}, [syncStatus.message, t])
|
||||
|
||||
const handleSetEnableSync = useCallback(flag => {
|
||||
setIsEnableSync(flag)
|
||||
|
||||
global.isSyncEnableing = true
|
||||
setIsWaiting(true)
|
||||
;(flag ? connect() : disconnect()).finally(() => {
|
||||
global.isSyncEnableing = false
|
||||
setIsWaiting(false)
|
||||
})
|
||||
}, [setIsEnableSync])
|
||||
|
||||
|
||||
const setHost = useCallback(host => {
|
||||
const newHostInfo = { ...hostInfo, host }
|
||||
setSyncHost(newHostInfo)
|
||||
setHostInfo(newHostInfo)
|
||||
}, [hostInfo])
|
||||
const setPort = useCallback(port => {
|
||||
const newHostInfo = { ...hostInfo, port }
|
||||
setSyncHost(newHostInfo)
|
||||
setHostInfo(newHostInfo)
|
||||
}, [hostInfo])
|
||||
|
||||
const host = useMemo(() => hostInfo.host, [hostInfo.host])
|
||||
const port = useMemo(() => hostInfo.port, [hostInfo.port])
|
||||
|
||||
const status = useMemo(() => {
|
||||
return `${syncStatus.message ? syncStatus.message : syncStatus.status ? t('setting_sync_status_enabled') : t('sync_status_disabled')}`
|
||||
}, [syncStatus.message, syncStatus.status, t])
|
||||
|
||||
const handleCancelSetCode = useCallback(() => {
|
||||
setVisibleCodeModal(false)
|
||||
}, [])
|
||||
const handleSetCode = useCallback(() => {
|
||||
const code = authCode.trim()
|
||||
if (code.length != 6) return
|
||||
connect(code)
|
||||
setAuthCode('')
|
||||
setVisibleCodeModal(false)
|
||||
}, [authCode])
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={{ marginTop: 5 }}>
|
||||
<CheckBoxItem disabled={isWaiting || !port || !host} check={isEnableSync} label={t('setting_sync_enbale')} onChange={handleSetEnableSync} />
|
||||
<Text style={{ color: theme.normal, marginLeft: 25, marginTop: 10, fontSize: 12 }}>{t('setting_sync_address', { address })}</Text>
|
||||
<Text style={{ color: theme.normal, marginLeft: 25, fontSize: 12 }}>{t('setting_sync_status', { status })}</Text>
|
||||
</View>
|
||||
<View style={{ marginTop: 10 }} >
|
||||
<HostInput setHost={setHost} host={host} disabled={isWaiting || isEnableSync} />
|
||||
<PortInput setPort={setPort} port={port} disabled={isWaiting || isEnableSync} />
|
||||
</View>
|
||||
<ConfirmAlert
|
||||
visible={visibleCodeModal}
|
||||
onHide={handleCancelSetCode}
|
||||
onConfirm={handleSetCode}
|
||||
>
|
||||
<View style={styles.authCodeContent}>
|
||||
<Text style={{ color: theme.normal, marginBottom: 5 }}>{t('setting_sync_code_label')}</Text>
|
||||
<Input
|
||||
placeholder={t('setting_sync_code_input_tip')}
|
||||
value={authCode}
|
||||
onChangeText={setAuthCode}
|
||||
style={{ ...styles.authCodeInput, backgroundColor: theme.secondary40 }}
|
||||
/>
|
||||
</View>
|
||||
</ConfirmAlert>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
authCodeContent: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
authCodeInput: {
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
minWidth: 240,
|
||||
borderRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
// tagTypeList: {
|
||||
// flexDirection: 'row',
|
||||
// flexWrap: 'wrap',
|
||||
// },
|
||||
// tagButton: {
|
||||
// // marginRight: 10,
|
||||
// borderRadius: 4,
|
||||
// marginRight: 10,
|
||||
// marginBottom: 10,
|
||||
// },
|
||||
// tagButtonText: {
|
||||
// paddingLeft: 12,
|
||||
// paddingRight: 12,
|
||||
// paddingTop: 8,
|
||||
// paddingBottom: 8,
|
||||
// },
|
||||
})
|
17
src/screens/Home/Setting/Sync/index.js
Normal file
17
src/screens/Home/Setting/Sync/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { memo } from 'react'
|
||||
|
||||
import Section from '../components/Section'
|
||||
import IsEnable from './IsEnable'
|
||||
// import SyncHost from './SyncHost'
|
||||
import { useTranslation } from '@/plugins/i18n'
|
||||
|
||||
export default memo(() => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Section title={t('setting_sync')}>
|
||||
<IsEnable />
|
||||
{/* <SyncHost /> */}
|
||||
</Section>
|
||||
)
|
||||
})
|
@ -18,7 +18,10 @@ export default memo(({ value, label, onChange, ...props }) => {
|
||||
useEffect(() => {
|
||||
isMountRef.current = true
|
||||
return () => isMountRef.current = false
|
||||
})
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (value != text) setText(String(value))
|
||||
}, [value])
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={{ ...styles.label, color: theme.normal }}>{label}</Text>
|
||||
@ -49,5 +52,6 @@ const styles = StyleSheet.create({
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
fontSize: 12,
|
||||
maxWidth: 300,
|
||||
},
|
||||
})
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
import Basic from './Basic'
|
||||
import Player from './Player'
|
||||
import List from './List'
|
||||
import Sync from './Sync'
|
||||
import Backup from './Backup'
|
||||
import Other from './Other'
|
||||
import Version from './Version'
|
||||
@ -30,6 +31,7 @@ export default () => {
|
||||
<Basic />
|
||||
<Player />
|
||||
<List />
|
||||
<Sync />
|
||||
<Backup />
|
||||
<Other />
|
||||
<Version />
|
||||
|
@ -35,6 +35,8 @@ export const TYPES = {
|
||||
setIsHandleAudioFocus: null,
|
||||
setAddMusicLocationType: null,
|
||||
setIsShowLyricTranslation: null,
|
||||
setIsEnableSync: null,
|
||||
setSyncStatus: null,
|
||||
}
|
||||
for (const key of Object.keys(TYPES)) {
|
||||
TYPES[key] = `common__${key}`
|
||||
@ -291,3 +293,18 @@ export const setIsShowLyricTranslation = flag => async(dispatch, getState) => {
|
||||
await setData(settingKey, common.setting)
|
||||
}
|
||||
|
||||
export const setIsEnableSync = flag => async(dispatch, getState) => {
|
||||
dispatch({
|
||||
type: TYPES.setIsEnableSync,
|
||||
payload: flag,
|
||||
})
|
||||
const { common } = getState()
|
||||
await setData(settingKey, common.setting)
|
||||
}
|
||||
|
||||
export const setSyncStatus = statusInfo => async(dispatch, getState) => {
|
||||
dispatch({
|
||||
type: TYPES.setSyncStatus,
|
||||
payload: statusInfo,
|
||||
})
|
||||
}
|
||||
|
@ -48,6 +48,9 @@ export const isShowLyricTranslation = state => state.common.setting.player.isSho
|
||||
|
||||
export const activeApiSourceId = state => state.common.setting.apiSource
|
||||
|
||||
export const isEnableSync = state => state.common.setting.sync.enable
|
||||
export const syncStatus = state => state.common.syncStatus
|
||||
|
||||
const apiSourceListFormated = apiSourceInfo.map(api => ({
|
||||
id: api.id,
|
||||
name: api.name,
|
||||
|
@ -39,6 +39,10 @@ const initialState = {
|
||||
desc: '',
|
||||
history: [],
|
||||
},
|
||||
syncStatus: {
|
||||
status: false,
|
||||
message: '',
|
||||
},
|
||||
componentIds: {},
|
||||
}
|
||||
|
||||
@ -310,6 +314,30 @@ const mutations = {
|
||||
|
||||
return newState
|
||||
},
|
||||
[TYPES.setIsEnableSync](state, isEnableSync) {
|
||||
const newState = {
|
||||
...state,
|
||||
setting: {
|
||||
...state.setting,
|
||||
sync: {
|
||||
...state.setting.sync,
|
||||
enable: isEnableSync,
|
||||
},
|
||||
},
|
||||
}
|
||||
return newState
|
||||
},
|
||||
[TYPES.setSyncStatus](state, { status, message }) {
|
||||
const newState = {
|
||||
...state,
|
||||
syncStatus: {
|
||||
...state.syncStatus,
|
||||
},
|
||||
}
|
||||
if (status != null) newState.syncStatus.status = status
|
||||
if (message != null) newState.syncStatus.message = message
|
||||
return newState
|
||||
},
|
||||
}
|
||||
|
||||
export default (state = initialState, action) =>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { action as playerAction } from '@/store/modules/player'
|
||||
import { action as commonAction } from '@/store/modules/common'
|
||||
import { findMusic } from '@/utils/music'
|
||||
import {
|
||||
getAllListData,
|
||||
@ -9,6 +10,7 @@ import {
|
||||
saveListScrollPosition,
|
||||
saveListSort,
|
||||
} from '@/utils/tools'
|
||||
import { list as listSync } from '@/plugins/sync'
|
||||
|
||||
export const TYPES = {
|
||||
initList: null,
|
||||
@ -29,6 +31,7 @@ export const TYPES = {
|
||||
setOtherSource: null,
|
||||
clearCache: null,
|
||||
jumpPosition: null,
|
||||
setSyncList: null,
|
||||
}
|
||||
|
||||
for (const key of Object.keys(TYPES)) {
|
||||
@ -57,7 +60,7 @@ export const initList = listData => async(dispatch, getState) => {
|
||||
listPosition = listData.listPosition
|
||||
listSort = listData.listSort
|
||||
}
|
||||
global.listScrollPosition = listPosition
|
||||
global.listScrollPosition = listPosition || {}
|
||||
global.listSort = listSort
|
||||
|
||||
let isNeedSaveSortInfo = false
|
||||
@ -79,9 +82,38 @@ export const initList = listData => async(dispatch, getState) => {
|
||||
type: TYPES.initList,
|
||||
payload: { defaultList, loveList, userList },
|
||||
})
|
||||
|
||||
// if (listData.isSync) {
|
||||
// const keys = Object.keys(global.allList)
|
||||
// dispatch(playerAction.checkPlayList(keys))
|
||||
// saveList(keys)
|
||||
// } else {
|
||||
// listSync.sendListAction('init_list', { defaultList, loveList, userList })
|
||||
// }
|
||||
}
|
||||
|
||||
export const setSyncList = ({ defaultList, loveList, userList }) => async(dispatch, getState) => {
|
||||
const state = getState()
|
||||
const userListIds = userList.map(l => l.id)
|
||||
const removeUserListIds = state.list.userList.filter(l => !userListIds.includes(l.id))
|
||||
if (removeUserListIds.includes(state.common.setting.list.prevSelectListId)) {
|
||||
dispatch(commonAction.setPrevSelectListId(state.list.defaultList.id))
|
||||
}
|
||||
dispatch({
|
||||
type: TYPES.setSyncList,
|
||||
payload: { defaultList, loveList, userList },
|
||||
})
|
||||
await removeList(removeUserListIds)
|
||||
|
||||
dispatch(playerAction.checkPlayList([...Object.keys(global.allList), ...removeUserListIds]))
|
||||
saveList([defaultList, loveList, ...userList])
|
||||
}
|
||||
|
||||
export const setList = ({ id, list, name, location, source, sourceListId, isSync }) => async(dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('set_list', { id, list, name, location, source, sourceListId })
|
||||
}
|
||||
|
||||
export const setList = ({ id, list, name, location, source, sourceListId }) => async(dispatch, getState) => {
|
||||
const targetList = global.allList[id]
|
||||
if (targetList) {
|
||||
if (name && targetList.name === name) {
|
||||
@ -89,17 +121,22 @@ export const setList = ({ id, list, name, location, source, sourceListId }) => a
|
||||
type: TYPES.listClear,
|
||||
payload: id,
|
||||
})
|
||||
dispatch(listAddMultiple({ id, list }))
|
||||
dispatch(listAddMultiple({ id, list, isSync: true }))
|
||||
return
|
||||
}
|
||||
|
||||
id += '_' + Math.random()
|
||||
}
|
||||
await dispatch(createUserList({ id, list, name, location, source, sourceListId }))
|
||||
await dispatch(createUserList({ id, list, name, location, source, sourceListId, isSync: true }))
|
||||
}
|
||||
|
||||
export const listAdd = ({ musicInfo, id, addMusicLocationType, isSync }) => (dispatch, getState) => {
|
||||
if (!addMusicLocationType) addMusicLocationType = getState().common.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_add', { id, musicInfo, addMusicLocationType })
|
||||
}
|
||||
|
||||
export const listAdd = ({ musicInfo, id }) => (dispatch, getState) => {
|
||||
const addMusicLocationType = getState().common.setting.list.addMusicLocationType
|
||||
dispatch({
|
||||
type: TYPES.listAdd,
|
||||
payload: {
|
||||
@ -112,18 +149,26 @@ export const listAdd = ({ musicInfo, id }) => (dispatch, getState) => {
|
||||
saveList(global.allList[id])
|
||||
}
|
||||
|
||||
export const listMove = ({ fromId, musicInfo, toId }) => (dispatch, getState) => {
|
||||
const addMusicLocationType = getState().common.setting.list.addMusicLocationType
|
||||
export const listMove = ({ fromId, musicInfo, toId, isSync }) => (dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_move', { fromId, musicInfo, toId })
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TYPES.listMove,
|
||||
payload: { fromId, musicInfo, toId, addMusicLocationType },
|
||||
payload: { fromId, musicInfo, toId },
|
||||
})
|
||||
dispatch(playerAction.checkPlayList([fromId, musicInfo]))
|
||||
dispatch(playerAction.checkPlayList([fromId, toId]))
|
||||
saveList([global.allList[fromId], global.allList[toId]])
|
||||
}
|
||||
|
||||
export const listAddMultiple = ({ id, list }) => (dispatch, getState) => {
|
||||
const addMusicLocationType = getState().common.setting.list.addMusicLocationType
|
||||
export const listAddMultiple = ({ id, list, addMusicLocationType, isSync }) => (dispatch, getState) => {
|
||||
if (!addMusicLocationType) addMusicLocationType = getState().common.setting.list.addMusicLocationType
|
||||
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_add_multiple', { id, list, addMusicLocationType })
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TYPES.listAddMultiple,
|
||||
payload: { id, list, addMusicLocationType },
|
||||
@ -132,10 +177,14 @@ export const listAddMultiple = ({ id, list }) => (dispatch, getState) => {
|
||||
saveList(global.allList[id])
|
||||
}
|
||||
|
||||
export const listMoveMultiple = ({ fromId, toId, list }) => (dispatch, getState) => {
|
||||
export const listMoveMultiple = ({ fromId, toId, list, isSync }) => (dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_move_multiple', { fromId, toId, list })
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TYPES.listRemoveMultiple,
|
||||
payload: { id: fromId, list },
|
||||
payload: { id: fromId, ids: list.map(s => s.songmid) },
|
||||
})
|
||||
dispatch({
|
||||
type: TYPES.listAddMultiple,
|
||||
@ -145,25 +194,37 @@ export const listMoveMultiple = ({ fromId, toId, list }) => (dispatch, getState)
|
||||
saveList([global.allList[fromId], global.allList[toId]])
|
||||
}
|
||||
|
||||
export const listRemove = ({ id, index }) => (dispatch, getState) => {
|
||||
export const listRemove = ({ listId, id, isSync }) => (dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_remove', { listId, id })
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TYPES.listRemove,
|
||||
payload: { id, index },
|
||||
payload: { listId, id },
|
||||
})
|
||||
dispatch(playerAction.checkPlayList([id]))
|
||||
saveList(global.allList[id])
|
||||
dispatch(playerAction.checkPlayList([listId]))
|
||||
saveList(global.allList[listId])
|
||||
}
|
||||
|
||||
export const listRemoveMultiple = ({ listId, ids, isSync }) => (dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_remove_multiple', { listId, ids })
|
||||
}
|
||||
|
||||
export const listRemoveMultiple = ({ id, list }) => (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: TYPES.listRemoveMultiple,
|
||||
payload: { id, list },
|
||||
payload: { listId, ids },
|
||||
})
|
||||
dispatch(playerAction.checkPlayList([id]))
|
||||
saveList(global.allList[id])
|
||||
dispatch(playerAction.checkPlayList([listId]))
|
||||
saveList(global.allList[listId])
|
||||
}
|
||||
|
||||
export const listClear = ({ id, isSync }) => (dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('list_clear', { id })
|
||||
}
|
||||
|
||||
export const listClear = id => (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: TYPES.listClear,
|
||||
payload: id,
|
||||
@ -172,35 +233,51 @@ export const listClear = id => (dispatch, getState) => {
|
||||
saveList(global.allList[id])
|
||||
}
|
||||
|
||||
export const updateMusicInfo = ({ id, index, data }) => (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: TYPES.updateMusicInfo,
|
||||
payload: { id, index, data },
|
||||
})
|
||||
saveList(global.allList[id])
|
||||
export const updateMusicInfo = ({ listId, id, data, isSync }) => (dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('update_music_info', { listId, id, data })
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TYPES.updateMusicInfo,
|
||||
payload: { listId, id, data },
|
||||
})
|
||||
saveList(global.allList[listId])
|
||||
}
|
||||
|
||||
export const createUserList = ({ name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, isSync }) => async(dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('create_user_list', { name, id, list, source, sourceListId })
|
||||
}
|
||||
|
||||
export const createUserList = ({ name, id = `userlist_${Date.now()}`, list = [], source, sourceListId }) => async(dispatch, getState) => {
|
||||
dispatch({
|
||||
type: TYPES.createUserList,
|
||||
payload: { name, id, source, sourceListId },
|
||||
})
|
||||
dispatch(listAddMultiple({ id, list }))
|
||||
dispatch(listAddMultiple({ id, list, isSync: true }))
|
||||
await saveList(global.allList[id])
|
||||
const state = getState()
|
||||
await saveListSort(id, state.list.userList.length)
|
||||
await saveListScrollPosition(id, 0)
|
||||
}
|
||||
|
||||
export const removeUserList = id => async(dispatch, getState) => {
|
||||
const { list } = getState()
|
||||
export const removeUserList = ({ id, isSync }) => async(dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('remove_user_list', { id })
|
||||
}
|
||||
|
||||
const { list, common } = getState()
|
||||
const index = list.userList.findIndex(l => l.id === id)
|
||||
if (index < 0) return
|
||||
if (common.setting.list.prevSelectListId == id) {
|
||||
dispatch(commonAction.setPrevSelectListId(list.defaultList.id))
|
||||
}
|
||||
dispatch({
|
||||
type: TYPES.removeUserList,
|
||||
payload: index,
|
||||
})
|
||||
await removeList(id)
|
||||
console.log(common.setting.list.prevSelectListId, id)
|
||||
dispatch(playerAction.checkPlayList([id]))
|
||||
}
|
||||
|
||||
@ -220,12 +297,17 @@ export const getOtherSource = ({ musicInfo, id }) => (dispatch, getState) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const setUserListName = ({ id, name }) => async(dispatch, getState) => {
|
||||
export const setUserListName = ({ id, name, isSync }) => async(dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('set_user_list_name', { id, name })
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TYPES.setUserListName,
|
||||
payload: { id, name },
|
||||
})
|
||||
await saveList(global.allList[id])
|
||||
const targetList = global.allList[id]
|
||||
await saveList(targetList)
|
||||
}
|
||||
export const setUserListPosition = ({ id, position }) => async(dispatch, getState) => {
|
||||
dispatch({
|
||||
@ -234,12 +316,15 @@ export const setUserListPosition = ({ id, position }) => async(dispatch, getStat
|
||||
})
|
||||
await saveList(global.allList[id])
|
||||
}
|
||||
export const setMusicPosition = ({ id, position, list }) => async(dispatch, getState) => {
|
||||
export const setMusicPosition = ({ id, position, list, isSync }) => async(dispatch, getState) => {
|
||||
if (!isSync) {
|
||||
listSync.sendListAction('set_music_position', { id, position, list })
|
||||
}
|
||||
// const targetList = global.allList[id]
|
||||
// if (!targetList) return
|
||||
dispatch({
|
||||
type: TYPES.listRemoveMultiple,
|
||||
payload: { id, list },
|
||||
payload: { listId: id, ids: list.map(m => m.songmid) },
|
||||
})
|
||||
dispatch({
|
||||
type: TYPES.setMusicPosition,
|
||||
|
@ -3,6 +3,9 @@ import { TYPES } from './action'
|
||||
const allList = global.allList = {}
|
||||
|
||||
const allListInit = (defaultList, loveList, userList) => {
|
||||
for (const id of Object.keys(allList)) {
|
||||
delete allList[id]
|
||||
}
|
||||
allList[defaultList.id] = defaultList
|
||||
allList[loveList.id] = loveList
|
||||
for (const list of userList) allList[list.id] = list
|
||||
@ -77,13 +80,26 @@ const mutations = {
|
||||
newState.loveList = { ...state.loveList, list: loveList.list, location: loveList.location }
|
||||
ids.push(loveList.id)
|
||||
}
|
||||
if (userList != null) newState.userList = userList
|
||||
if (userList != null) {
|
||||
newState.userList = userList
|
||||
for (const list of userList) {
|
||||
ids.push(list.id)
|
||||
}
|
||||
}
|
||||
allListInit(newState.defaultList, newState.loveList, newState.userList)
|
||||
newState.isInitedList = true
|
||||
return updateStateList(newState, [ids])
|
||||
// console.log(allList.default, newState, ids)
|
||||
// return newState
|
||||
},
|
||||
[TYPES.setSyncList](state, { defaultList, loveList, userList }) {
|
||||
const newState = { ...state }
|
||||
newState.defaultList = defaultList
|
||||
newState.loveList = loveList
|
||||
newState.userList = userList
|
||||
allListInit(newState.defaultList, newState.loveList, newState.userList)
|
||||
return newState
|
||||
},
|
||||
/* [TYPES.initList](state, { defaultList, loveList, userList }) {
|
||||
const newState = { ...state }
|
||||
if (defaultList != null) newState.defaultList = { ...state.defaultList, list: defaultList.list, location: defaultList.location }
|
||||
@ -154,36 +170,44 @@ const mutations = {
|
||||
const targetList = allList[id]
|
||||
if (!targetList) return state
|
||||
let newList
|
||||
const map = {}
|
||||
const ids = []
|
||||
switch (addMusicLocationType) {
|
||||
case 'top':
|
||||
newList = [...list, ...targetList.list]
|
||||
for (let i = newList.length - 1; i > -1; i--) {
|
||||
const item = newList[i]
|
||||
if (map[item.songmid]) continue
|
||||
ids.unshift(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
default:
|
||||
newList = [...targetList.list, ...list]
|
||||
break
|
||||
}
|
||||
const map = {}
|
||||
const ids = []
|
||||
for (const item of newList) {
|
||||
if (map[item.songmid]) continue
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
targetList.list = ids.map(id => map[id])
|
||||
return updateStateList({ ...state }, [id])
|
||||
},
|
||||
[TYPES.listRemove](state, { id, index }) {
|
||||
const targetList = allList[id]
|
||||
[TYPES.listRemove](state, { listId, id }) {
|
||||
const targetList = allList[listId]
|
||||
// console.log(targetList, id, index)
|
||||
if (!targetList) return state
|
||||
const index = targetList.list.findIndex(item => item.songmid == id)
|
||||
if (index < 0) return state
|
||||
const newTargetList = [...targetList.list]
|
||||
newTargetList.splice(index, 1)
|
||||
targetList.list = newTargetList
|
||||
return updateStateList({ ...state }, [id])
|
||||
return updateStateList({ ...state }, [listId])
|
||||
},
|
||||
[TYPES.listRemoveMultiple](state, { id, list }) {
|
||||
const targetList = allList[id]
|
||||
[TYPES.listRemoveMultiple](state, { listId, ids: musicIds }) {
|
||||
const targetList = allList[listId]
|
||||
if (!targetList) return state
|
||||
const map = {}
|
||||
const ids = []
|
||||
@ -191,14 +215,14 @@ const mutations = {
|
||||
ids.push(item.songmid)
|
||||
map[item.songmid] = item
|
||||
}
|
||||
for (const item of list) {
|
||||
if (map[item.songmid]) delete map[item.songmid]
|
||||
for (const songmid of musicIds) {
|
||||
if (map[songmid]) delete map[songmid]
|
||||
}
|
||||
const newList = []
|
||||
for (const id of ids) if (map[id]) newList.push(map[id])
|
||||
|
||||
targetList.list = newList
|
||||
return updateStateList({ ...state }, [id])
|
||||
return updateStateList({ ...state }, [listId])
|
||||
},
|
||||
[TYPES.listClear](state, id) {
|
||||
const targetList = allList[id]
|
||||
@ -206,13 +230,15 @@ const mutations = {
|
||||
targetList.list = []
|
||||
return updateStateList({ ...state }, [id])
|
||||
},
|
||||
[TYPES.updateMusicInfo](state, { id, index, data }) {
|
||||
const targetList = allList[id]
|
||||
[TYPES.updateMusicInfo](state, { listId, id, data }) {
|
||||
const targetList = allList[listId]
|
||||
if (!targetList) return state
|
||||
const targetMusicInfo = targetList.list.find(item => item.songmid == id)
|
||||
if (!targetMusicInfo) return state
|
||||
const newTargetList = [...targetList.list]
|
||||
Object.assign(newTargetList[index], data)
|
||||
Object.assign(targetMusicInfo, data)
|
||||
targetList.list = newTargetList
|
||||
return updateStateList({ ...state }, [id])
|
||||
return updateStateList({ ...state }, [listId])
|
||||
},
|
||||
|
||||
[TYPES.createUserList](state, { name, id, source, sourceListId }) {
|
||||
|
@ -46,7 +46,9 @@ const mutations = {
|
||||
[TYPES.setList](state, list) {
|
||||
return {
|
||||
...state,
|
||||
listInfo: list,
|
||||
listInfo: {
|
||||
...list,
|
||||
},
|
||||
}
|
||||
},
|
||||
[TYPES.setPlayIndex](state, index) {
|
||||
|
@ -6,6 +6,8 @@ import { throttle } from './index'
|
||||
|
||||
const playInfoStorageKey = storageDataPrefix.playInfo
|
||||
const listPositionPrefix = storageDataPrefix.listPosition
|
||||
const syncAuthKeyPrefix = storageDataPrefix.syncAuthKey
|
||||
const syncHostPrefix = storageDataPrefix.syncHost
|
||||
const listPrefix = storageDataPrefix.list
|
||||
const listSortPrefix = storageDataPrefix.listSort
|
||||
const defaultListKey = listPrefix + 'default'
|
||||
@ -209,6 +211,35 @@ export const removeListScrollPosition = async listIds => {
|
||||
handleSaveListScrollPosition(global.listScrollPosition)
|
||||
}
|
||||
|
||||
export const getSyncAuthKey = async serverId => {
|
||||
const keys = await getData(syncAuthKeyPrefix)
|
||||
if (!keys) return null
|
||||
return keys[serverId] || null
|
||||
}
|
||||
|
||||
export const setSyncAuthKey = async(serverId, key) => {
|
||||
let keys = await getData(syncAuthKeyPrefix) || {}
|
||||
keys[serverId] = key
|
||||
await setData(syncAuthKeyPrefix, keys)
|
||||
}
|
||||
|
||||
let syncHostInfo
|
||||
export const getSyncHost = async() => {
|
||||
if (syncHostInfo === undefined) {
|
||||
syncHostInfo = await getData(syncHostPrefix) || { host: '', port: '' }
|
||||
}
|
||||
return { ...syncHostInfo }
|
||||
}
|
||||
|
||||
export const setSyncHost = async({ host, port }) => {
|
||||
// let hostInfo = await getData(syncHostPrefix) || {}
|
||||
// hostInfo.host = host
|
||||
// hostInfo.port = port
|
||||
syncHostInfo.host = host
|
||||
syncHostInfo.port = port
|
||||
await setData(syncHostPrefix, syncHostInfo)
|
||||
}
|
||||
|
||||
export const exitApp = BackHandler.exitApp
|
||||
|
||||
export {
|
||||
|
@ -20,3 +20,9 @@ export const screenUnkeepAwake = () => {
|
||||
global.isScreenKeepAwake = false
|
||||
UtilsModule.screenUnkeepAwake()
|
||||
}
|
||||
|
||||
export const getWIFIIPV4Address = UtilsModule.getWIFIIPV4Address
|
||||
|
||||
export const getDeviceName = () => {
|
||||
return UtilsModule.getDeviceName().then(deviceName => deviceName || 'Unknown')
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user