├── .bundle └── config ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── feature.yml ├── actions │ ├── setup │ │ └── action.yml │ └── upload-artifact │ │ └── action.yml └── workflows │ └── release.yml ├── .gitignore ├── .ncurc.js ├── .nvmrc ├── .prettierrc.js ├── .vscode ├── i18n-ally-custom-framework.yml ├── javascript.code-snippets └── settings.json ├── CHANGELOG.md ├── FAQ.md ├── Gemfile ├── LICENSE ├── README.md ├── android ├── app │ ├── build.gradle │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ ├── fonts │ │ │ │ └── icomoon.ttf │ │ │ └── script │ │ │ │ └── user-api-preload.js │ │ ├── java │ │ │ └── com │ │ │ │ └── ikunshare │ │ │ │ └── music │ │ │ │ └── mobile │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainApplication.java │ │ │ │ ├── cache │ │ │ │ ├── CacheClearAsyncTask.java │ │ │ │ ├── CacheModule.java │ │ │ │ ├── CachePackage.java │ │ │ │ └── Utils.java │ │ │ │ ├── crypto │ │ │ │ ├── AES.java │ │ │ │ ├── CryptoModule.java │ │ │ │ ├── CryptoPackage.java │ │ │ │ └── RSA.java │ │ │ │ ├── lyric │ │ │ │ ├── Lyric.java │ │ │ │ ├── LyricEvent.java │ │ │ │ ├── LyricModule.java │ │ │ │ ├── LyricPackage.java │ │ │ │ ├── LyricPlayer.java │ │ │ │ ├── LyricSwitchView.java │ │ │ │ ├── LyricTextView.java │ │ │ │ ├── LyricView.java │ │ │ │ └── Utils.java │ │ │ │ ├── userApi │ │ │ │ ├── Console.java │ │ │ │ ├── HandlerWhat.java │ │ │ │ ├── JavaScriptThread.java │ │ │ │ ├── JsHandler.java │ │ │ │ ├── QuickJS.java │ │ │ │ ├── UserApiModule.java │ │ │ │ ├── UserApiPackage.java │ │ │ │ └── UtilsEvent.java │ │ │ │ └── utils │ │ │ │ ├── AsyncTask.java │ │ │ │ ├── BatteryOptimizationUtil.java │ │ │ │ ├── Utils.java │ │ │ │ ├── UtilsEvent.java │ │ │ │ ├── UtilsModule.java │ │ │ │ └── UtilsPackage.java │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-land-hdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-land-mdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-land-xhdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-land-xxhdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-land-xxxhdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-mdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-xhdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-xxhdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── launch_screen.png │ │ │ ├── drawable │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── rn_edit_text_material.xml │ │ │ └── rounded_corner.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── file_paths.xml │ │ │ └── network_security_config.xml │ │ └── release │ │ └── java │ │ └── com │ │ └── ikunshare │ │ └── music │ │ └── ReactNativeFlipper.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── dependencies-patch.js ├── doc └── images │ └── icon.png ├── index.js ├── metro.config.js ├── package-lock.json ├── package.json ├── publish ├── changeLog.md ├── index.js ├── utils │ ├── index.js │ └── updateChangeLog.js └── version.json ├── shim.js ├── src ├── app.ts ├── components │ ├── DesktopLyricEnable.tsx │ ├── MetadataEditModal │ │ ├── InputItem.tsx │ │ ├── MetadataForm.tsx │ │ ├── ParseName.tsx │ │ ├── PicItem.tsx │ │ ├── TextAreaItem.tsx │ │ └── index.tsx │ ├── MusicAddModal │ │ ├── CreateUserList.tsx │ │ ├── List.tsx │ │ ├── ListItem.tsx │ │ ├── MusicAddModal.tsx │ │ ├── Title.tsx │ │ └── index.tsx │ ├── MusicMultiAddModal │ │ ├── List.tsx │ │ ├── ListItem.tsx │ │ ├── MusicMultiAddModal.tsx │ │ ├── Title.tsx │ │ └── index.tsx │ ├── OnlineList │ │ ├── List.tsx │ │ ├── ListItem.tsx │ │ ├── ListMenu.tsx │ │ ├── MultipleModeBar.tsx │ │ ├── index.tsx │ │ └── listAction.ts │ ├── PageContent.tsx │ ├── SearchTipList │ │ ├── List.tsx │ │ └── index.tsx │ ├── SizeView.tsx │ ├── SourceSelector.tsx │ ├── TimeoutExitEditModal.tsx │ ├── common │ │ ├── Badge.tsx │ │ ├── Button.tsx │ │ ├── ButtonPrimary.tsx │ │ ├── CheckBox │ │ │ ├── Checkbox.tsx │ │ │ └── index.tsx │ │ ├── ChoosePath │ │ │ ├── List.tsx │ │ │ ├── components │ │ │ │ ├── Footer.tsx │ │ │ │ ├── Header.tsx │ │ │ │ ├── ListItem.tsx │ │ │ │ ├── Main.tsx │ │ │ │ ├── NewFolderModal.tsx │ │ │ │ └── OpenStorageModal.tsx │ │ │ └── index.tsx │ │ ├── ConfirmAlert.tsx │ │ ├── Dialog.tsx │ │ ├── DorpDownMenu.tsx │ │ ├── DorpDownPanel │ │ │ ├── Panel.tsx │ │ │ └── index.tsx │ │ ├── DrawerLayoutFixed.tsx │ │ ├── FileSelect.tsx │ │ ├── Icon.tsx │ │ ├── Image.tsx │ │ ├── ImageBackground.tsx │ │ ├── Input.tsx │ │ ├── Loading.tsx │ │ ├── LoadingMask.tsx │ │ ├── Menu.tsx │ │ ├── Modal.tsx │ │ ├── Popup.tsx │ │ ├── ScaledImage.tsx │ │ ├── Slider.tsx │ │ ├── StatusBar.tsx │ │ └── Text.tsx │ └── player │ │ ├── PlayerBar │ │ ├── components │ │ │ ├── ControlBtn.tsx │ │ │ ├── Pic.tsx │ │ │ ├── PlayInfo.tsx │ │ │ ├── Status.tsx │ │ │ └── Title.tsx │ │ └── index.tsx │ │ ├── Progress.tsx │ │ └── ProgressBar.tsx ├── config │ ├── constant.ts │ ├── defaultSetting.ts │ ├── globalData.ts │ ├── index.js │ ├── migrate.ts │ ├── migrateSetting.ts │ └── setting.ts ├── core │ ├── apiSource.ts │ ├── common.ts │ ├── desktopLyric.ts │ ├── dislikeList.ts │ ├── hotSearch.ts │ ├── init │ │ ├── common.ts │ │ ├── dataInit.ts │ │ ├── deeplink │ │ │ ├── fileAction.ts │ │ │ ├── index.ts │ │ │ ├── musicAction.js │ │ │ ├── playSonglist.ts │ │ │ ├── playerAction.ts │ │ │ ├── songlistAction.js │ │ │ └── utils.js │ │ ├── i18n.ts │ │ ├── index.ts │ │ ├── player │ │ │ ├── index.ts │ │ │ ├── lyric.ts │ │ │ ├── playInfo.ts │ │ │ ├── playProgress.ts │ │ │ ├── playStatus.ts │ │ │ ├── player.ts │ │ │ ├── playerEvent.ts │ │ │ ├── preloadNextMusic.ts │ │ │ └── watchList.ts │ │ ├── sync.ts │ │ ├── theme.ts │ │ └── userApi │ │ │ ├── index.ts │ │ │ └── request.js │ ├── leaderboard.ts │ ├── list.ts │ ├── lyric.ts │ ├── music │ │ ├── download.ts │ │ ├── index.ts │ │ ├── local.ts │ │ ├── online.ts │ │ └── utils.ts │ ├── player │ │ ├── playInfo.ts │ │ ├── playStatus.ts │ │ ├── playedList.ts │ │ ├── player.ts │ │ ├── progress.ts │ │ ├── tempPlayList.ts │ │ ├── timeoutExit.ts │ │ └── utils.ts │ ├── search │ │ ├── music.ts │ │ ├── search.ts │ │ └── songlist.ts │ ├── songlist.ts │ ├── sync.ts │ ├── syncSourceList.ts │ ├── theme.ts │ ├── userApi.ts │ └── version.ts ├── event │ ├── Event.ts │ ├── appEvent.ts │ ├── dislikeEvent.ts │ ├── listEvent.ts │ └── stateEvent.ts ├── lang │ ├── Readme.md │ ├── en-us.json │ ├── i18n.ts │ ├── index.ts │ ├── zh-cn.json │ └── zh-tw.json ├── navigation │ ├── components │ │ ├── ModalContent.tsx │ │ ├── PactModal.tsx │ │ ├── SyncModeModal.tsx │ │ ├── Toast.js │ │ └── VersionModal.tsx │ ├── event.ts │ ├── hooks.ts │ ├── index.ts │ ├── navigation.ts │ ├── regLaunchedEvent.ts │ ├── registerScreens.tsx │ ├── screenNames.ts │ └── utils.ts ├── plugins │ ├── lyric.ts │ ├── player │ │ ├── hook.ts │ │ ├── index.ts │ │ ├── playList.ts │ │ ├── service.ts │ │ └── utils.ts │ ├── storage.ts │ └── sync │ │ ├── client │ │ ├── auth.ts │ │ ├── client.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── dislike │ │ │ │ ├── handler.ts │ │ │ │ ├── index.ts │ │ │ │ └── localEvent.ts │ │ │ ├── index.ts │ │ │ └── list │ │ │ │ ├── handler.ts │ │ │ │ ├── index.ts │ │ │ │ └── localEvent.ts │ │ ├── sync │ │ │ ├── handler.ts │ │ │ └── index.ts │ │ └── utils.ts │ │ ├── constants.ts │ │ ├── data.ts │ │ ├── dislikeEvent.ts │ │ ├── index.ts │ │ ├── listEvent.ts │ │ ├── log.ts │ │ └── utils.ts ├── resources │ ├── fonts │ │ ├── icomoon.ttf │ │ └── selection.json │ ├── images │ │ ├── defaultUser.jpg │ │ ├── notification.hdpi.png │ │ ├── notification.ldpi.png │ │ ├── notification.mdpi.png │ │ ├── notification.xhdpi.png │ │ ├── notification2.hdpi.png │ │ ├── notification2.ldpi.png │ │ ├── notification2.mdpi.png │ │ └── notification2.xhdpi.png │ └── medias │ │ └── Silence02s.mp3 ├── screens │ ├── Comment │ │ ├── CommentHot.tsx │ │ ├── CommentNew.tsx │ │ ├── components │ │ │ ├── CommentFloor.tsx │ │ │ ├── CommentImage.tsx │ │ │ ├── CommentText.tsx │ │ │ ├── Header.tsx │ │ │ └── List.tsx │ │ ├── index.tsx │ │ └── utils.ts │ ├── Home │ │ ├── Horizontal │ │ │ ├── Aside.tsx │ │ │ ├── Header.tsx │ │ │ ├── Main.tsx │ │ │ └── index.tsx │ │ ├── Vertical │ │ │ ├── Content.tsx │ │ │ ├── DrawerNav.tsx │ │ │ ├── Header.tsx │ │ │ ├── Main.tsx │ │ │ └── index.tsx │ │ ├── Views │ │ │ ├── Download │ │ │ │ └── index.js │ │ │ ├── Leaderboard │ │ │ │ ├── BoardsList │ │ │ │ │ ├── List.tsx │ │ │ │ │ ├── ListItem.tsx │ │ │ │ │ ├── ListMenu.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Horizontal │ │ │ │ │ ├── LeftBar.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── MusicList.tsx │ │ │ │ ├── Vertical │ │ │ │ │ ├── HeaderBar │ │ │ │ │ │ ├── ActiveListName.tsx │ │ │ │ │ │ ├── SourceSelector.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── listAction.ts │ │ │ ├── Mylist │ │ │ │ ├── MusicList │ │ │ │ │ ├── ActiveList.tsx │ │ │ │ │ ├── List.tsx │ │ │ │ │ ├── ListItem.tsx │ │ │ │ │ ├── ListMenu.tsx │ │ │ │ │ ├── ListMusicSearch.tsx │ │ │ │ │ ├── ListSearchBar.tsx │ │ │ │ │ ├── MultipleModeBar.tsx │ │ │ │ │ ├── MusicDownloadModal.tsx │ │ │ │ │ ├── MusicPositionModal.tsx │ │ │ │ │ ├── MusicToggleModal.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── listAction.ts │ │ │ │ ├── MyList │ │ │ │ │ ├── DuplicateMusic.tsx │ │ │ │ │ ├── List.tsx │ │ │ │ │ ├── ListImportExport.tsx │ │ │ │ │ ├── ListMenu.tsx │ │ │ │ │ ├── ListMusicSort.tsx │ │ │ │ │ ├── ListNameEdit.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── listAction.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── index.tsx │ │ │ ├── Search │ │ │ │ ├── BlankView │ │ │ │ │ ├── HistorySearch.tsx │ │ │ │ │ ├── HotSearch.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── HeaderBar │ │ │ │ │ ├── SearchInput.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── List.tsx │ │ │ │ ├── MusicList.tsx │ │ │ │ ├── SearchTypeSelector.tsx │ │ │ │ ├── SonglistList.tsx │ │ │ │ ├── TipList.tsx │ │ │ │ └── index.tsx │ │ │ ├── Setting │ │ │ │ ├── Horizontal │ │ │ │ │ ├── NavList.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Main.tsx │ │ │ │ ├── Vertical │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── Main.tsx │ │ │ │ │ ├── NavList.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── components │ │ │ │ │ ├── Button.tsx │ │ │ │ │ ├── CheckBoxItem.tsx │ │ │ │ │ ├── InputItem.tsx │ │ │ │ │ ├── Section.tsx │ │ │ │ │ ├── Slider.tsx │ │ │ │ │ └── SubTitle.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── settings │ │ │ │ │ ├── About.tsx │ │ │ │ │ ├── Backup │ │ │ │ │ ├── All.js │ │ │ │ │ ├── ListImportExport.tsx │ │ │ │ │ ├── Part.tsx │ │ │ │ │ ├── actions.ts │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Basic │ │ │ │ │ ├── DrawerLayoutPosition.tsx │ │ │ │ │ ├── FontSize.tsx │ │ │ │ │ ├── IsAlwaysKeepStatusbarHeight.tsx │ │ │ │ │ ├── IsAutoHidePlayBar.tsx │ │ │ │ │ ├── IsHomePageScroll.tsx │ │ │ │ │ ├── IsShowBackBtn.tsx │ │ │ │ │ ├── IsShowExitBtn.tsx │ │ │ │ │ ├── IsStartupAutoPlay.tsx │ │ │ │ │ ├── IsStartupPushPlayDetailScreen.tsx │ │ │ │ │ ├── IsUseSystemFileSelector.tsx │ │ │ │ │ ├── Language.tsx │ │ │ │ │ ├── ShareType.tsx │ │ │ │ │ ├── Source.tsx │ │ │ │ │ ├── SourceName.tsx │ │ │ │ │ ├── UserApiEditModal │ │ │ │ │ │ ├── ImportBtn.tsx │ │ │ │ │ │ ├── List.tsx │ │ │ │ │ │ ├── ScriptImportExport.tsx │ │ │ │ │ │ ├── ScriptImportOnline.tsx │ │ │ │ │ │ ├── action.ts │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── List │ │ │ │ │ ├── AddMusicLocationType.tsx │ │ │ │ │ ├── IsClickPlayList.tsx │ │ │ │ │ ├── IsShowAlbumName.tsx │ │ │ │ │ ├── IsShowInterval.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── LyricDesktop │ │ │ │ │ ├── IsLockLyric.tsx │ │ │ │ │ ├── IsShowLyric.tsx │ │ │ │ │ ├── IsShowToggleAnima.tsx │ │ │ │ │ ├── IsSingleLine.tsx │ │ │ │ │ ├── MaxLineNum.tsx │ │ │ │ │ ├── TextOpacity.tsx │ │ │ │ │ ├── TextPositionX.tsx │ │ │ │ │ ├── TextPositionY.tsx │ │ │ │ │ ├── TextSize.tsx │ │ │ │ │ ├── Theme.tsx │ │ │ │ │ ├── ViewWidth.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Other │ │ │ │ │ ├── DislikeEditModal.tsx │ │ │ │ │ ├── DislikeList.tsx │ │ │ │ │ ├── Log.tsx │ │ │ │ │ ├── MetaCache.tsx │ │ │ │ │ ├── ResourceCache.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Player │ │ │ │ │ ├── IsAutoCleanPlayedList.tsx │ │ │ │ │ ├── IsEnableAudioOffload.tsx │ │ │ │ │ ├── IsHandleAudioFocus.tsx │ │ │ │ │ ├── IsS2T.tsx │ │ │ │ │ ├── IsSavePlayTime.tsx │ │ │ │ │ ├── IsShowBluetoothLyric.tsx │ │ │ │ │ ├── IsShowLyricRoma.tsx │ │ │ │ │ ├── IsShowLyricTranslation.tsx │ │ │ │ │ ├── IsShowNotificationImage.tsx │ │ │ │ │ ├── MaxCache.tsx │ │ │ │ │ ├── PlayHighQuality.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Search │ │ │ │ │ ├── IsShowHistorySearch.tsx │ │ │ │ │ ├── IsShowHotSearch.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Sync │ │ │ │ │ ├── History.tsx │ │ │ │ │ ├── IsEnable.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── isEnable.tsx.bak │ │ │ │ │ ├── Theme │ │ │ │ │ ├── IsAutoTheme.tsx │ │ │ │ │ ├── IsDynamicBg.tsx │ │ │ │ │ ├── IsFontShadow.tsx │ │ │ │ │ ├── IsHideBgDark.tsx │ │ │ │ │ ├── Theme.tsx │ │ │ │ │ └── index.tsx │ │ │ │ │ └── Version.tsx │ │ │ └── SongList │ │ │ │ ├── Content.tsx │ │ │ │ ├── HeaderBar │ │ │ │ ├── OpenList │ │ │ │ │ ├── Modal.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── SortTab.tsx │ │ │ │ ├── SourceSelector.tsx │ │ │ │ ├── Tag │ │ │ │ │ ├── CurrentTagBtn.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── List.tsx │ │ │ │ ├── TagList │ │ │ │ ├── List.tsx │ │ │ │ ├── TagGroup.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── components │ │ │ │ └── Songlist │ │ │ │ │ ├── List.tsx │ │ │ │ │ ├── ListItem.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── PlayDetail │ │ ├── Horizontal │ │ │ ├── Lyric.tsx │ │ │ ├── MoreBtn │ │ │ │ ├── Btn.tsx │ │ │ │ ├── MusicAddBtn.tsx │ │ │ │ ├── PlayModeBtn.tsx │ │ │ │ ├── TimeoutExitBtn.tsx │ │ │ │ └── index.tsx │ │ │ ├── Pic.tsx │ │ │ ├── Player │ │ │ │ ├── ControlBtn.tsx │ │ │ │ ├── PlayInfo.tsx │ │ │ │ ├── Status.tsx │ │ │ │ └── index.tsx │ │ │ ├── components │ │ │ │ ├── Btn.tsx │ │ │ │ ├── CommentBtn.tsx │ │ │ │ ├── DesktopLyricBtn.tsx │ │ │ │ └── Header.tsx │ │ │ ├── constant.ts │ │ │ └── index.tsx │ │ ├── Vertical │ │ │ ├── Lyric.tsx │ │ │ ├── Pic.tsx │ │ │ ├── Player │ │ │ │ ├── components │ │ │ │ │ ├── ControlBtn.tsx │ │ │ │ │ ├── MoreBtn │ │ │ │ │ │ ├── Btn.tsx │ │ │ │ │ │ ├── CommentBtn.tsx │ │ │ │ │ │ ├── DesktopLyricBtn.tsx │ │ │ │ │ │ ├── MusicAddBtn.tsx │ │ │ │ │ │ ├── PlayModeBtn.tsx │ │ │ │ │ │ ├── TimeoutExitBtn.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── PlayInfo.tsx │ │ │ │ │ └── Status.tsx │ │ │ │ └── index.tsx │ │ │ ├── components │ │ │ │ ├── Btn.tsx │ │ │ │ ├── Header.tsx │ │ │ │ └── TimeoutExitBtn.tsx │ │ │ └── index.tsx │ │ ├── components │ │ │ ├── PlayLine.tsx │ │ │ └── SettingPopup │ │ │ │ ├── index.tsx │ │ │ │ └── settings │ │ │ │ ├── SettingLrcAlign.tsx │ │ │ │ ├── SettingLrcFontSize.tsx │ │ │ │ ├── SettingLyricProgress.tsx │ │ │ │ ├── SettingPlaybackRate.tsx │ │ │ │ ├── SettingVolume.tsx │ │ │ │ └── style.ts │ │ └── index.tsx │ ├── SonglistDetail │ │ ├── ActionBar.tsx │ │ ├── Header.tsx │ │ ├── MusicList.tsx │ │ ├── index.tsx │ │ ├── listAction.ts │ │ └── state.ts │ └── index.ts ├── store │ ├── Provider │ │ ├── Provider.tsx │ │ ├── ThemeProvider.tsx │ │ └── index.ts │ ├── common │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts │ ├── dislikeList │ │ ├── action.ts │ │ ├── event.ts │ │ ├── hook.ts │ │ ├── index.ts │ │ └── state.ts │ ├── hotSearch │ │ ├── action.ts │ │ └── state.ts │ ├── index.ts │ ├── leaderboard │ │ ├── action.ts │ │ └── state.ts │ ├── list │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts │ ├── player │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts │ ├── search │ │ ├── action.ts │ │ ├── music │ │ │ ├── action.ts │ │ │ └── state.ts │ │ ├── songlist │ │ │ ├── action.ts │ │ │ └── state.ts │ │ └── state.ts │ ├── setting │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts │ ├── songlist │ │ ├── action.ts │ │ └── state.ts │ ├── sync │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts │ ├── theme │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts │ ├── userApi │ │ ├── action.ts │ │ ├── event.ts │ │ ├── hook.ts │ │ ├── index.ts │ │ └── state.ts │ └── version │ │ ├── action.ts │ │ ├── hook.ts │ │ └── state.ts ├── theme │ ├── Colors.js │ ├── Typography.js │ ├── index.js │ └── themes │ │ ├── colorUtils.js │ │ ├── createThemes.js │ │ ├── images │ │ ├── china_ink.jpg │ │ ├── jqbg.jpg │ │ ├── landingMoon2.png │ │ ├── myzcbg.jpg │ │ └── xnkl.png │ │ ├── index.ts │ │ ├── themes.ts │ │ └── utils.js ├── types │ ├── app.d.ts │ ├── app_setting.d.ts │ ├── common.d.ts │ ├── config_files.d.ts │ ├── dislike_list.d.ts │ ├── dislike_list_sync.d.ts │ ├── download_list.d.ts │ ├── list.d.ts │ ├── list_sync.d.ts │ ├── music.d.ts │ ├── player.d.ts │ ├── sync.d.ts │ ├── sync_common.d.ts │ ├── theme.d.ts │ ├── user_api.d.ts │ └── utils.d.ts └── utils │ ├── bootLog.ts │ ├── common.ts │ ├── data.ts │ ├── dislikeManage.ts │ ├── errorHandle.ts │ ├── fs.ts │ ├── hooks │ ├── index.js │ ├── useAnimateColor.ts │ ├── useAnimateNumber.ts │ ├── useAssertApiSupport.js │ ├── useBackHandler.ts │ ├── useDeviceOrientation.js │ ├── useDrag.ts │ ├── useHorizontalMode.ts │ ├── useKeyboard.js │ ├── useLayout.tsx │ ├── usePlayTime.js │ ├── useUnmounted.tsx │ └── useWindowSize.ts │ ├── index.ts │ ├── listManage.ts │ ├── localMediaMetadata.ts │ ├── log.ts │ ├── message.ts │ ├── music.ts │ ├── musicSdk │ ├── api-source-info.ts │ ├── api-source.js │ ├── index.js │ ├── kg │ │ ├── album.js │ │ ├── comment.js │ │ ├── hotSearch.js │ │ ├── index.js │ │ ├── leaderboard.js │ │ ├── lyric.js │ │ ├── musicInfo.js │ │ ├── musicSearch.js │ │ ├── pic.js │ │ ├── quality_detail.js │ │ ├── singer.js │ │ ├── songList.js │ │ ├── temp │ │ │ ├── musicSearch-new.js │ │ │ └── songList-new.js │ │ ├── tipSearch.js │ │ ├── util.js │ │ └── vendors │ │ │ └── infSign.min.js │ ├── kw │ │ ├── album.js │ │ ├── api-mobi.js │ │ ├── comment.js │ │ ├── decodeLyric.js │ │ ├── hotSearch.js │ │ ├── index.js │ │ ├── leaderboard.js │ │ ├── lyric.js │ │ ├── musicSearch.js │ │ ├── pic.js │ │ ├── songList.js │ │ ├── tipSearch.js │ │ └── util.js │ ├── mg │ │ ├── album.js │ │ ├── comment.js │ │ ├── hotSearch.js │ │ ├── index.js │ │ ├── leaderboard.js │ │ ├── lyric.js │ │ ├── musicInfo.js │ │ ├── musicSearch.js │ │ ├── pic.js │ │ ├── songId.js │ │ ├── songList.js │ │ ├── temp │ │ │ └── leaderboard-old.js │ │ ├── tipSearch.js │ │ └── utils │ │ │ ├── index.js │ │ │ └── mrc.js │ ├── options.js │ ├── tx │ │ ├── comment.js │ │ ├── hotSearch.js │ │ ├── index.js │ │ ├── leaderboard.js │ │ ├── lyric.js │ │ ├── musicInfo.js │ │ ├── musicSearch.js │ │ ├── quality_detail.js │ │ ├── songList.js │ │ └── tipSearch.js │ ├── utils.js │ └── wy │ │ ├── comment.js │ │ ├── hotSearch.js │ │ ├── index.js │ │ ├── leaderboard.js │ │ ├── lyric.js │ │ ├── musicDetail.js │ │ ├── musicInfo.js │ │ ├── musicSearch.js │ │ ├── quality_detail.js │ │ ├── songList.js │ │ ├── tipSearch.js │ │ └── utils │ │ ├── crypto.js │ │ └── index.js │ ├── nativeModules │ ├── cache.ts │ ├── crypto.ts │ ├── cryptoTest.ts │ ├── lyricDesktop.ts │ ├── userApi.ts │ └── utils.ts │ ├── pixelRatio.ts │ ├── request.js │ ├── scroll.ts │ ├── simplify-chinese-main │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── chinese.js │ ├── index.d.ts │ ├── index.js │ └── package.json │ ├── tools.ts │ ├── version.js │ └── windowSizeTools.ts ├── test.js └── tsconfig.json /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: ✨ 功能请求 2 | description: 为这个项目提出一个想法,请先查看常见问题及搜索 Issue 列表中有无你要提的问题。 3 | title: '[Feature]: ' 4 | body: 5 | - type: checkboxes 6 | id: check-answer 7 | attributes: 8 | label: 解决方案检查 9 | description: 请确保你已完成以下所有操作。 10 | options: 11 | - label: 我已阅读 [常见问题](https://lyswhut.github.io/lx-music-doc/mobile/faq),但没有找到解决方案。 12 | required: true 13 | - label: 我已搜索 [Issue 列表](https://github.com/ikunshare/ikun-music-mobile/issues?q=is%3Aissue+),但没有发现类似的问题。 14 | required: true 15 | - type: textarea 16 | id: problem-description 17 | attributes: 18 | label: 问题描述 19 | description: 请添加清晰简洁的描述,说明你希望通过此功能请求解决的问题。 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: proposed-solution 24 | attributes: 25 | label: 描述你想要的解决方案 26 | description: 简洁明了地描述你要发生的事情。 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: alternatives-considered 31 | attributes: 32 | label: 描述你考虑过的替代方案 33 | description: 对你考虑过的所有替代解决方案或功能的简洁明了的描述。 34 | validations: 35 | required: false 36 | - type: textarea 37 | id: additional-information 38 | attributes: 39 | label: 附加信息 40 | description: 如果你的问题需要进一步解释,或者想要表达其他内容,请在此处添加更多信息。(直接把图片/视频拖到编辑框即可添加图片/视频) 41 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Env 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@v4 9 | with: 10 | node-version-file: .nvmrc 11 | 12 | - name: Setup Java Env 13 | uses: actions/setup-java@v4 14 | with: 15 | distribution: 'microsoft' 16 | java-version: '17' 17 | cache: gradle 18 | 19 | - name: Get npm cache directory 20 | id: npm-cache-dir 21 | shell: bash 22 | run: echo "cachedir=$(npm config get cache)" >> ${GITHUB_OUTPUT} 23 | 24 | - name: Cache node modules 25 | id: cache-npm 26 | uses: actions/cache@v4 27 | with: 28 | # npm cache files are stored in `~/.npm` on Linux/macOS 29 | path: ${{ steps.npm-cache-dir.outputs.cachedir }} 30 | key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }} 31 | restore-keys: | 32 | ${{ runner.os }}-npm-cache- 33 | 34 | - name: Install dependencies 35 | shell: bash 36 | run: npm ci 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | ios/.xcode.env.local 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | keystore.properties 32 | *.iml 33 | *.hprof 34 | .cxx/ 35 | 36 | # node.js 37 | # 38 | node_modules/ 39 | npm-debug.log 40 | yarn-error.log 41 | 42 | # BUCK 43 | buck-out/ 44 | \.buckd/ 45 | *.keystore 46 | !debug.keystore 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | **/fastlane/report.xml 56 | **/fastlane/Preview.html 57 | **/fastlane/screenshots 58 | **/fastlane/test_output 59 | 60 | # Bundle artifact 61 | *.jsbundle 62 | 63 | # Ruby / CocoaPods 64 | /ios/Pods/ 65 | /vendor/bundle/ 66 | 67 | # testing 68 | /coverage 69 | -------------------------------------------------------------------------------- /.ncurc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | upgrade: true, 3 | reject: [ 4 | '@types/react', 5 | '@types/react-native', 6 | 'message2call', 7 | 'react', 8 | 'react-native', 9 | // 'react-native-pager-view', 10 | 'react-native-navigation', 11 | '@react-native/metro-config', 12 | '@react-native/babel-preset', 13 | '@react-native/typescript-config', 14 | ], 15 | 16 | // target: 'newest', 17 | // filter: [ 18 | // 'react-native-navigation', 19 | // ], 20 | 21 | // target: 'patch', 22 | // filter: [ 23 | // '@types/react', 24 | // '@types/react-native', 25 | // 'react', 26 | // 'react-native', 27 | // '@react-native/metro-config', 28 | // '@react-native/babel-preset', 29 | // '@react-native/typescript-config', 30 | // ], 31 | } 32 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | bracketSpacing: true, 4 | semi: false, 5 | singleQuote: true, 6 | printWidth: 100, 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/i18n-ally-custom-framework.yml: -------------------------------------------------------------------------------- 1 | # .vscode/i18n-ally-custom-framework.yml 2 | 3 | # An array of strings which contain Language Ids defined by VS Code 4 | # You can check avaliable language ids here: https://code.visualstudio.com/docs/languages/overview#_language-id 5 | languageIds: 6 | - javascript 7 | - javascriptreact 8 | - typescript 9 | - typescriptreact 10 | 11 | # An array of RegExes to find the key usage. **The key should be captured in the first match group**. 12 | # You should unescape RegEx strings in order to fit in the YAML file 13 | # To help with this, you can use https://www.freeformatter.com/json-escape.html 14 | usageMatchRegex: 15 | # The following example shows how to detect `t("your.i18n.keys")` 16 | # the `{key}` will be placed by a proper keypath matching regex, 17 | # you can ignore it and use your own matching rules as well 18 | - "[^\\w\\d]t\\(['\"`]({key})['\"`]" 19 | 20 | # An array of strings containing refactor templates. 21 | # The "$1" will be replaced by the keypath specified. 22 | # Optional: uncomment the following two lines to use 23 | 24 | # refactorTemplates: 25 | # - i18n.get("$1") 26 | 27 | # If set to true, only enables this custom framework (will disable all built-in frameworks) 28 | monopoly: true 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": ["src/lang"], 3 | // "i18n-ally.fullReloadOnChanged": true, 4 | "i18n-ally.keystyle": "nested", 5 | "i18n-ally.displayLanguage": "zh-cn", 6 | "i18n-ally.sourceLanguage": "zh-cn", 7 | "i18n-ally.translate.engines": ["google-cn", "google"], 8 | "i18n-ally.sortKeys": true 9 | } 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | gem 'cocoapods', '~> 1.12' 7 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | -keep class com.reactnativenavigation.views.element.animators.** { *; } 13 | # -keepclassmembers class com.reactnativenavigation.views.element.animators.** { *; } 14 | 15 | 16 | -keep class org.jaudiotagger.tag.** { *; } 17 | 18 | 19 | -keep public class com.dylanvann.fastimage.* {*;} 20 | -keep public class com.dylanvann.fastimage.** {*;} 21 | -keep public class * implements com.bumptech.glide.module.GlideModule 22 | -keep public class * extends com.bumptech.glide.module.AppGlideModule 23 | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { 24 | **[] $VALUES; 25 | public *; 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile; 2 | 3 | import com.reactnativenavigation.NavigationActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 7 | 8 | public class MainActivity extends NavigationActivity { 9 | 10 | 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/cache/CacheClearAsyncTask.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.cache; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import com.facebook.react.bridge.Promise; 6 | 7 | // https://github.com/midas-gufei/react-native-clear-app-cache/tree/master/android/src/main/java/com/learnta/clear 8 | public class CacheClearAsyncTask extends AsyncTask { 9 | public CacheModule cacheModule = null; 10 | public Promise promise; 11 | public CacheClearAsyncTask(CacheModule clearCacheModule, Promise promise) { 12 | super(); 13 | this.cacheModule = clearCacheModule; 14 | this.promise = promise; 15 | } 16 | 17 | @Override 18 | protected void onPreExecute() { 19 | super.onPreExecute(); 20 | } 21 | 22 | @Override 23 | protected void onPostExecute(String s) { 24 | super.onPostExecute(s); 25 | promise.resolve(null); 26 | } 27 | 28 | @Override 29 | protected String doInBackground(Integer... params) { 30 | cacheModule.clearCache(); 31 | return null; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/cache/CachePackage.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.cache; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class CachePackage implements ReactPackage { 13 | 14 | @Override 15 | public List createViewManagers(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List createNativeModules(ReactApplicationContext reactContext) { 21 | return Arrays.asList(new CacheModule(reactContext)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/crypto/CryptoPackage.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.crypto; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class CryptoPackage implements ReactPackage { 13 | 14 | @Override 15 | public List createViewManagers(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List createNativeModules(ReactApplicationContext reactContext) { 21 | return Arrays.asList(new CryptoModule(reactContext)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/lyric/LyricEvent.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.lyric; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.modules.core.DeviceEventManagerModule; 8 | 9 | public class LyricEvent { 10 | final String SET_VIEW_POSITION = "set-position"; 11 | final String LYRIC_Line_PLAY = "lyric-line-play"; 12 | 13 | private final ReactApplicationContext reactContext; 14 | LyricEvent(ReactApplicationContext reactContext) { this.reactContext = reactContext; } 15 | 16 | public void sendEvent(String eventName, @Nullable WritableMap params) { 17 | // Log.d("Lyric", "senEvent: " + eventName); 18 | reactContext 19 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 20 | .emit(eventName, params); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/lyric/LyricPackage.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.lyric; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class LyricPackage implements ReactPackage { 13 | @Override 14 | public List createViewManagers(ReactApplicationContext reactContext) { 15 | return Collections.emptyList(); 16 | } 17 | 18 | @Override 19 | public List createNativeModules(ReactApplicationContext reactContext) { 20 | return Arrays.asList(new LyricModule(reactContext)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/lyric/Utils.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.lyric; 2 | 3 | import android.os.Handler; 4 | 5 | public class Utils { 6 | // https://gist.github.com/mathew-kurian/2bd2b8b3a2f6438d6786 7 | public static Object setTimeout(Runnable runnable, long delay) { 8 | return new TimeoutEvent(runnable, delay); 9 | } 10 | public static void clearTimeout(Object timeoutEvent) { 11 | if (timeoutEvent instanceof TimeoutEvent) { 12 | ((TimeoutEvent) timeoutEvent).cancelTimeout(); 13 | } 14 | } 15 | private static class TimeoutEvent { 16 | private static final Handler handler = new Handler(); 17 | private volatile Runnable runnable; 18 | 19 | private TimeoutEvent(Runnable task, long delay) { 20 | runnable = task; 21 | handler.postDelayed(() -> { 22 | if (runnable != null) { 23 | runnable.run(); 24 | } 25 | }, delay); 26 | } 27 | 28 | private void cancelTimeout() { 29 | runnable = null; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/userApi/Console.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.userApi; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | import android.util.Log; 6 | 7 | import com.whl.quickjs.wrapper.QuickJSContext; 8 | 9 | public class Console implements QuickJSContext.Console { 10 | private final Handler eventHandler; 11 | 12 | Console(Handler eventHandler) { 13 | this.eventHandler = eventHandler; 14 | } 15 | 16 | private void sendLog(String type, String log) { 17 | Message message = this.eventHandler.obtainMessage(); 18 | message.what = HandlerWhat.LOG; 19 | message.obj = new Object[]{type, log}; 20 | Log.d("UserApi Log", "[" + type + "]" + log); 21 | this.eventHandler.sendMessage(message); 22 | } 23 | 24 | @Override 25 | public void log(String info) { 26 | sendLog("log", info); 27 | } 28 | 29 | @Override 30 | public void info(String info) { 31 | sendLog("info", info); 32 | } 33 | 34 | @Override 35 | public void warn(String info) { 36 | sendLog("warn", info); 37 | } 38 | 39 | @Override 40 | public void error(String info) { 41 | sendLog("error", info); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/userApi/HandlerWhat.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.userApi; 2 | 3 | public class HandlerWhat { 4 | public static final int ACTION = 1000; 5 | public static final int INIT = 99; 6 | public static final int INIT_FAILED = 500; 7 | public static final int INIT_SUCCESS = 200; 8 | public static final int DESTROY = 98; 9 | public static final int LOG = 1001; 10 | } 11 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/userApi/UserApiPackage.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.userApi; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class UserApiPackage implements ReactPackage { 12 | @Override 13 | public List createViewManagers(ReactApplicationContext reactContext) { 14 | return Collections.emptyList(); 15 | } 16 | 17 | @Override 18 | public List createNativeModules(ReactApplicationContext reactContext) { 19 | return Arrays.asList(new UserApiModule(reactContext)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/userApi/UtilsEvent.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.userApi; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.WritableMap; 9 | import com.facebook.react.modules.core.DeviceEventManagerModule; 10 | 11 | public class UtilsEvent { 12 | final String API_ACTION = "api-action"; 13 | private final ReactApplicationContext reactContext; 14 | 15 | UtilsEvent(ReactApplicationContext reactContext) { 16 | this.reactContext = reactContext; 17 | } 18 | 19 | public void sendEvent(String eventName, @Nullable WritableMap params) { 20 | reactContext 21 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 22 | .emit(eventName, params); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.utils; 2 | 3 | 4 | import android.content.Context; 5 | import android.os.storage.StorageManager; 6 | 7 | import com.facebook.react.bridge.Promise; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.bridge.ReactMethod; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.InputStreamReader; 18 | import java.lang.reflect.Array; 19 | import java.lang.reflect.Method; 20 | import java.util.ArrayList; 21 | import java.util.Objects; 22 | import java.util.concurrent.Callable; 23 | 24 | public class Utils { 25 | // public static boolean deletePath(File dir) { 26 | // if (dir.isDirectory()) { 27 | // String[] children = dir.list(); 28 | // for (int i=0; i< children.length; i++) { 29 | // boolean success = deletePath(new File(dir, children[i])); 30 | // if (!success) { 31 | // return false; 32 | // } 33 | // } 34 | // } 35 | // 36 | // // The directory is now empty so delete it 37 | // return dir.delete(); 38 | // } 39 | } 40 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/utils/UtilsEvent.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.utils; 2 | 3 | import android.util.Log; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.WritableMap; 9 | import com.facebook.react.modules.core.DeviceEventManagerModule; 10 | 11 | public class UtilsEvent { 12 | final String SCREEN_STATE = "screen-state"; 13 | final String SCREEN_SIZE_CHANGED = "screen-size-changed"; 14 | 15 | private final ReactApplicationContext reactContext; 16 | UtilsEvent(ReactApplicationContext reactContext) { this.reactContext = reactContext; } 17 | 18 | public void sendEvent(String eventName, @Nullable WritableMap params) { 19 | reactContext 20 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 21 | .emit(eventName, params); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ikunshare/music/mobile/utils/UtilsPackage.java: -------------------------------------------------------------------------------- 1 | package com.ikunshare.music.mobile.utils; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.ViewManager; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class UtilsPackage implements ReactPackage { 13 | 14 | @Override 15 | public List createViewManagers(ReactApplicationContext reactContext) { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List createNativeModules(ReactApplicationContext reactContext) { 21 | return Arrays.asList(new UtilsModule(reactContext)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-hdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-hdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-land-hdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-mdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-land-mdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xhdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-land-xhdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxhdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-land-xxhdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxxhdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-land-xxxhdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-mdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-xhdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-xxhdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/launch_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/rounded_corner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | IKUN Music 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/release/java/com/ikunshare/music/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.ikunshare.music.mobile; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "35.0.0" 6 | minSdkVersion = 21 7 | compileSdkVersion = 35 8 | targetSdkVersion = 29 9 | 10 | ndkVersion = "26.1.10909125" 11 | kotlinVersion = "1.9.24" // Or any version above 1.3.x 12 | RNNKotlinVersion = kotlinVersion 13 | 14 | // https://github.com/DylanVann/react-native-fast-image/blob/9ab80fcd570b7f56da66ab20e52c9a35934067c9/docs/app-glide-module.md 15 | excludeAppGlideModule = true 16 | } 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | dependencies { 22 | classpath('com.android.tools.build:gradle:8.6.1') 23 | classpath("com.facebook.react:react-native-gradle-plugin") 24 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") 25 | } 26 | } 27 | 28 | apply plugin: "com.facebook.react.rootproject" 29 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'com.ikunshare.music.mobile' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') 5 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IKUN Music", 3 | "displayName": "洛雪音乐助手" 4 | } 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:@react-native/babel-preset'], 3 | plugins: [ 4 | '@babel/plugin-proposal-export-namespace-from', 5 | [ 6 | 'module-resolver', 7 | { 8 | root: ['.'], 9 | extensions: [ 10 | '.android.ts', 11 | '.ios.ts', 12 | '.android.tsx', 13 | '.ios.tsx', 14 | '.tsx', 15 | '.ts', 16 | '.android.js', 17 | '.ios.js', 18 | '.android.jsx', 19 | '.ios.jsx', 20 | '.jsx', 21 | '.js', 22 | '.json', 23 | ], 24 | alias: { 25 | '@': './src', 26 | // '@config': './src/config', 27 | // '@store': './src/store', 28 | // '@components': './src/components', 29 | // '@navigation': './src/navigation', 30 | // '@screens': './src/screens', 31 | // '@theme': './src/theme', 32 | }, 33 | }, 34 | ], 35 | ], 36 | } 37 | -------------------------------------------------------------------------------- /dependencies-patch.js: -------------------------------------------------------------------------------- 1 | // 修补依赖源码以使构建的依赖恢复正常工作 2 | 3 | const fs = require('node:fs') 4 | const path = require('node:path') 5 | 6 | const rootPath = path.join(__dirname, './') 7 | 8 | const patchs = [] 9 | 10 | ;(async () => { 11 | for (const [filePath, fromStr, toStr] of patchs) { 12 | console.log(`Patching ${filePath.replace(rootPath, '')}`) 13 | try { 14 | const file = (await fs.promises.readFile(filePath)).toString() 15 | await fs.promises.writeFile(filePath, file.replace(fromStr, toStr)) 16 | } catch (err) { 17 | console.error(`Patch ${filePath.replace(rootPath, '')} failed: ${err.message}`) 18 | } 19 | } 20 | console.log('\nDependencies patch finished.\n') 21 | })() 22 | -------------------------------------------------------------------------------- /doc/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/doc/images/icon.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | import './shim' 5 | import './src/app' 6 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config') 2 | 3 | /** 4 | * Metro configuration 5 | * https://facebook.github.io/metro/docs/configuration 6 | * 7 | * @type {import('metro-config').MetroConfig} 8 | */ 9 | const config = { 10 | resolver: { 11 | extraNodeModules: { 12 | // crypto: require.resolve('react-native-quick-crypto'), 13 | // stream: require.resolve('stream-browserify'), 14 | buffer: require.resolve('@craftzdog/react-native-buffer'), 15 | }, 16 | }, 17 | } 18 | 19 | module.exports = mergeConfig(getDefaultConfig(__dirname), config) 20 | -------------------------------------------------------------------------------- /publish/changeLog.md: -------------------------------------------------------------------------------- 1 | ### 修复 2 | 3 | - 修复 tx 歌单搜索名字、描述出现乱码的问题 4 | - 修复解析某些本地歌词文件时出现乱码的问题(#694) 5 | 6 | ### 优化 7 | 8 | - 优化软件文案编排(#701, #703, @3gf8jv4dv) 9 | 10 | ### 其他 11 | 12 | - 更新项目文档(@3gf8jv4dv) 13 | -------------------------------------------------------------------------------- /shim.js: -------------------------------------------------------------------------------- 1 | global.Buffer = require('buffer').Buffer 2 | -------------------------------------------------------------------------------- /src/components/MetadataEditModal/InputItem.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | import { StyleSheet, View } from 'react-native' 4 | import type { InputProps } from '@/components/common/Input' 5 | import Input from '@/components/common/Input' 6 | import { useTheme } from '@/store/theme/hook' 7 | import Text from '@/components/common/Text' 8 | 9 | export interface InputItemProps extends InputProps { 10 | value: string 11 | label: string 12 | onChanged: (text: string) => void 13 | } 14 | 15 | export default memo(({ value, label, onChanged, ...props }: InputItemProps) => { 16 | const theme = useTheme() 17 | return ( 18 | 19 | 20 | {label} 21 | 22 | 28 | 29 | ) 30 | }) 31 | 32 | const styles = StyleSheet.create({ 33 | container: { 34 | // paddingLeft: 25, 35 | marginBottom: 15, 36 | }, 37 | label: { 38 | marginBottom: 2, 39 | }, 40 | input: { 41 | flexGrow: 1, 42 | flexShrink: 1, 43 | // borderRadius: 4, 44 | // paddingTop: 3, 45 | // paddingBottom: 3, 46 | // maxWidth: 300, 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/MusicAddModal/Title.tsx: -------------------------------------------------------------------------------- 1 | import Text from '@/components/common/Text' 2 | import { createStyle } from '@/utils/tools' 3 | import { useTheme } from '@/store/theme/hook' 4 | import { useI18n } from '@/lang' 5 | 6 | export default ({ musicInfo, isMove }: { musicInfo: LX.Music.MusicInfo; isMove: boolean }) => { 7 | const theme = useTheme() 8 | const t = useI18n() 9 | return ( 10 | 11 | {t(isMove ? 'list_add_title_first_move' : 'list_add_title_first_add')}{' '} 12 | {musicInfo.name} {t('list_add_title_last')} 13 | 14 | ) 15 | } 16 | 17 | const styles = createStyle({ 18 | title: { 19 | textAlign: 'center', 20 | paddingTop: 15, 21 | paddingBottom: 15, 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /src/components/MusicAddModal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useImperativeHandle, forwardRef, useState } from 'react' 2 | import Modal, { 3 | type MusicAddModalType as ModalType, 4 | type MusicAddModalProps as ModalProps, 5 | type SelectInfo, 6 | } from './MusicAddModal' 7 | 8 | export interface MusicAddModalProps { 9 | onAdded?: ModalProps['onAdded'] 10 | } 11 | export interface MusicAddModalType { 12 | show: (info: SelectInfo) => void 13 | } 14 | 15 | export default forwardRef(({ onAdded }, ref) => { 16 | const musicAddModalRef = useRef(null) 17 | const [visible, setVisible] = useState(false) 18 | 19 | useImperativeHandle(ref, () => ({ 20 | show(listInfo) { 21 | if (visible) musicAddModalRef.current?.show(listInfo) 22 | else { 23 | setVisible(true) 24 | requestAnimationFrame(() => { 25 | musicAddModalRef.current?.show(listInfo) 26 | }) 27 | } 28 | }, 29 | })) 30 | 31 | return visible ? : null 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/MusicMultiAddModal/Title.tsx: -------------------------------------------------------------------------------- 1 | import Text from '@/components/common/Text' 2 | import { createStyle } from '@/utils/tools' 3 | import { useTheme } from '@/store/theme/hook' 4 | import { useI18n } from '@/lang' 5 | 6 | export default ({ 7 | selectedList, 8 | isMove, 9 | }: { 10 | selectedList: LX.Music.MusicInfo[] 11 | isMove: boolean 12 | }) => { 13 | const theme = useTheme() 14 | const t = useI18n() 15 | return ( 16 | 17 | {t(isMove ? 'list_multi_add_title_first_move' : 'list_multi_add_title_first_add')}{' '} 18 | 19 | {selectedList.length} 20 | {' '} 21 | {t('list_multi_add_title_last')} 22 | 23 | ) 24 | } 25 | 26 | const styles = createStyle({ 27 | title: { 28 | textAlign: 'center', 29 | paddingTop: 15, 30 | paddingBottom: 15, 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/MusicMultiAddModal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useImperativeHandle, forwardRef, useState } from 'react' 2 | import Modal, { 3 | type MusicMultiAddModalType as ModalType, 4 | type MusicMultiAddModalProps as ModalProps, 5 | type SelectInfo, 6 | } from './MusicMultiAddModal' 7 | 8 | export interface MusicAddModalProps { 9 | onAdded?: ModalProps['onAdded'] 10 | } 11 | export interface MusicMultiAddModalType { 12 | show: (info: SelectInfo) => void 13 | } 14 | 15 | export default forwardRef(({ onAdded }, ref) => { 16 | const musicMultiAddModalRef = useRef(null) 17 | const [visible, setVisible] = useState(false) 18 | 19 | useImperativeHandle(ref, () => ({ 20 | show(listInfo) { 21 | if (visible) musicMultiAddModalRef.current?.show(listInfo) 22 | else { 23 | setVisible(true) 24 | requestAnimationFrame(() => { 25 | musicMultiAddModalRef.current?.show(listInfo) 26 | }) 27 | } 28 | }, 29 | })) 30 | 31 | return visible ? : null 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/SearchTipList/List.tsx: -------------------------------------------------------------------------------- 1 | import { useState, forwardRef, useImperativeHandle, type Ref } from 'react' 2 | import { FlatList, type FlatListProps } from 'react-native' 3 | 4 | // import InsetShadow from 'react-native-inset-shadow' 5 | 6 | export type ItemT = FlatListProps['data'] 7 | 8 | export type ListProps = Pick< 9 | FlatListProps, 10 | | 'renderItem' 11 | | 'maxToRenderPerBatch' 12 | | 'windowSize' 13 | | 'initialNumToRender' 14 | | 'keyExtractor' 15 | | 'getItemLayout' 16 | | 'keyboardShouldPersistTaps' 17 | > 18 | 19 | export interface ListType { 20 | setList: (list: T[]) => void 21 | } 22 | 23 | const List = >(props: ListProps, ref: Ref>) => { 24 | const [list, setList] = useState([]) 25 | useImperativeHandle(ref, () => ({ 26 | setList(list) { 27 | setList(list) 28 | }, 29 | })) 30 | 31 | return ( 32 | 38 | ) 39 | } 40 | 41 | export default forwardRef(List) as ( 42 | p: ListProps & { ref?: Ref> } 43 | ) => JSX.Element | null 44 | -------------------------------------------------------------------------------- /src/components/common/ButtonPrimary.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | import Button, { type BtnProps } from '@/components/common/Button' 4 | import Text from '@/components/common/Text' 5 | import { useTheme } from '@/store/theme/hook' 6 | import { createStyle } from '@/utils/tools' 7 | 8 | export interface ButtonProps extends BtnProps { 9 | size?: number 10 | } 11 | 12 | export default memo(({ disabled, size = 14, onPress, children }: ButtonProps) => { 13 | const theme = useTheme() 14 | 15 | return ( 16 | 25 | ) 26 | }) 27 | 28 | const styles = createStyle({ 29 | button: { 30 | paddingHorizontal: 10, 31 | paddingVertical: 5, 32 | borderRadius: 4, 33 | marginRight: 10, 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /src/components/common/FileSelect.tsx: -------------------------------------------------------------------------------- 1 | import ChoosePath, { type ReadOptions, type ChoosePathType } from '@/components/common/ChoosePath' 2 | import { forwardRef, useImperativeHandle, useRef, useState } from 'react' 3 | 4 | export interface FileSelectType { 5 | show: (options: ReadOptions, onSelect: typeof noop) => void 6 | } 7 | const noop = (path: string) => {} 8 | export default forwardRef((props, ref) => { 9 | const [visible, setVisible] = useState(false) 10 | const choosePathRef = useRef(null) 11 | const onSelectRef = useRef(noop) 12 | // console.log('render import export') 13 | 14 | useImperativeHandle(ref, () => ({ 15 | show(options, onSelect) { 16 | onSelectRef.current = onSelect ?? noop 17 | if (visible) { 18 | choosePathRef.current?.show(options) 19 | } else { 20 | setVisible(true) 21 | requestAnimationFrame(() => { 22 | choosePathRef.current?.show(options) 23 | }) 24 | } 25 | }, 26 | })) 27 | 28 | return visible ? : null 29 | }) 30 | -------------------------------------------------------------------------------- /src/components/common/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | import { createStyle } from '@/utils/tools' 4 | import { useTheme } from '@/store/theme/hook' 5 | import { ActivityIndicator, StyleSheet, type ActivityIndicatorProps } from 'react-native' 6 | import { setSpText } from '@/utils/pixelRatio' 7 | import Text from './Text' 8 | 9 | export interface LoadingProps extends Omit { 10 | size?: number 11 | label?: string 12 | } 13 | 14 | const LoadingLabel = ({ style, label, ...props }: LoadingProps) => { 15 | return ( 16 | <> 17 | 18 | 19 | {label} 20 | 21 | 22 | ) 23 | } 24 | 25 | export default memo(({ size = 15, label, ...props }: LoadingProps) => { 26 | const theme = useTheme() 27 | 28 | return label ? ( 29 | 30 | ) : ( 31 | 32 | ) 33 | }) 34 | 35 | const styles = createStyle({ 36 | loadingLabel: { 37 | marginRight: 6, 38 | }, 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/common/StatusBar.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@/store/theme/hook' 2 | import { StatusBar as RNStatusBar } from 'react-native' 3 | 4 | const StatusBar = function () { 5 | const theme = useTheme() 6 | const statusBarStyle = theme.isDark ? 'light-content' : 'dark-content' 7 | return ( 8 | 9 | ) 10 | } 11 | 12 | StatusBar.currentHeight = RNStatusBar.currentHeight ?? 0 13 | StatusBar.setBarStyle = RNStatusBar.setBarStyle 14 | 15 | export default StatusBar 16 | -------------------------------------------------------------------------------- /src/components/player/PlayerBar/components/Status.tsx: -------------------------------------------------------------------------------- 1 | import { useLrcPlay } from '@/plugins/lyric' 2 | import { useIsPlay, useStatusText } from '@/store/player/hook' 3 | // import { createStyle } from '@/utils/tools' 4 | import Text from '@/components/common/Text' 5 | 6 | export default ({ autoUpdate }: { autoUpdate: boolean }) => { 7 | const { text } = useLrcPlay(autoUpdate) 8 | const statusText = useStatusText() 9 | const isPlay = useIsPlay() 10 | // console.log('render status') 11 | 12 | const status = isPlay ? text : statusText 13 | 14 | return ( 15 | 16 | {status} 17 | 18 | ) 19 | } 20 | 21 | // const styles = createStyle({ 22 | // text: { 23 | // // fontSize: 10, 24 | // // lineHeight: 18, 25 | // // height: 18, 26 | // // height: '100%', 27 | // // backgroundColor: 'rgba(0,0,0,0.2)', 28 | // }, 29 | // }) 30 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | // import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource' 2 | import defaultUrl from '@/resources/medias/Silence02s.mp3' 3 | import notificationIcon from '@/resources/images/notification.xhdpi.png' 4 | // const defaultUrl = resolveAssetSource(resourceDefaultUrl).uri 5 | 6 | export { defaultUrl, notificationIcon } 7 | // export const defaultUrl = require('@/resources/medias/Silence02s.mp3') 8 | -------------------------------------------------------------------------------- /src/core/dislikeList.ts: -------------------------------------------------------------------------------- 1 | import { action } from '@/store/dislikeList' 2 | 3 | export const addDislikeInfo = async (infos: LX.Dislike.DislikeMusicInfo[]) => { 4 | await global.dislike_event.dislike_music_add(infos) 5 | } 6 | 7 | export const overwirteDislikeInfo = async (rules: string) => { 8 | await global.dislike_event.dislike_data_overwrite(rules) 9 | } 10 | 11 | export const clearDislikeInfo = async () => { 12 | await global.dislike_event.dislike_music_clear() 13 | } 14 | 15 | export const hasDislike = (info: LX.Music.MusicInfo | LX.Download.ListItem | null) => { 16 | if (!info) return false 17 | return action.hasDislike(info) 18 | } 19 | 20 | export const setDislikeInfo = (info: LX.Dislike.DislikeInfo) => { 21 | action.setDislikeInfo(info) 22 | } 23 | 24 | export { getDislikeInfo } from '@/utils/dislikeManage' 25 | -------------------------------------------------------------------------------- /src/core/init/deeplink/playerAction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | collectMusic, 3 | dislikeMusic, 4 | pause, 5 | play, 6 | playNext, 7 | playPrev, 8 | togglePlay, 9 | uncollectMusic, 10 | } from '@/core/player/player' 11 | 12 | export type PlayerAction = 13 | | 'play' 14 | | 'pause' 15 | | 'skipNext' 16 | | 'skipPrev' 17 | | 'togglePlay' 18 | | 'collect' 19 | | 'uncollect' 20 | | 'dislike' 21 | 22 | export const handlePlayerAction = async (action: PlayerAction) => { 23 | switch (action) { 24 | case 'play': 25 | play() 26 | break 27 | case 'pause': 28 | void pause() 29 | break 30 | case 'skipNext': 31 | void playNext() 32 | break 33 | case 'skipPrev': 34 | void playPrev() 35 | break 36 | case 'togglePlay': 37 | togglePlay() 38 | break 39 | case 'collect': 40 | collectMusic() 41 | break 42 | case 'uncollect': 43 | uncollectMusic() 44 | break 45 | case 'dislike': 46 | void dislikeMusic() 47 | break 48 | // default: throw new Error('Unknown action: ' + (action as any ?? '')) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/core/init/i18n.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from '@/lang/i18n' 2 | import type { I18n } from '@/lang/i18n' 3 | import { getDeviceLanguage } from '@/utils/tools' 4 | import { setLanguage, updateSetting } from '@/core/common' 5 | 6 | export default async (setting: LX.AppSetting) => { 7 | let lang = setting['common.langId'] 8 | 9 | global.i18n = createI18n() 10 | 11 | if (!lang || !global.i18n.availableLocales.includes(lang)) { 12 | const deviceLanguage = (await getDeviceLanguage()).toLowerCase() 13 | if ( 14 | typeof deviceLanguage == 'string' && 15 | global.i18n.availableLocales.includes(deviceLanguage as I18n['locale']) 16 | ) { 17 | lang = deviceLanguage as I18n['locale'] 18 | } else { 19 | lang = 'en_us' 20 | } 21 | updateSetting({ 'common.langId': lang }) 22 | } 23 | setLanguage(lang) 24 | } 25 | -------------------------------------------------------------------------------- /src/core/init/player/index.ts: -------------------------------------------------------------------------------- 1 | import initPlayer from './player' 2 | import initPlayInfo from './playInfo' 3 | import initPlayStatus from './playStatus' 4 | import initPlayerEvent from './playerEvent' 5 | import initWatchList from './watchList' 6 | import initPlayProgress from './playProgress' 7 | import initPreloadNextMusic from './preloadNextMusic' 8 | import initLyric from './lyric' 9 | 10 | export default async (setting: LX.AppSetting) => { 11 | await initPlayer(setting) 12 | await initLyric(setting) 13 | await initPlayInfo(setting) 14 | initPlayStatus() 15 | initPlayerEvent() 16 | initWatchList() 17 | initPlayProgress() 18 | initPreloadNextMusic() 19 | } 20 | -------------------------------------------------------------------------------- /src/core/init/sync.ts: -------------------------------------------------------------------------------- 1 | import { connectServer } from '@/plugins/sync' 2 | import { updateSetting } from '@/core/common' 3 | import { getSyncHost } from '@/plugins/sync/data' 4 | 5 | export default async (setting: LX.AppSetting) => { 6 | if (!setting['sync.enable']) return 7 | 8 | const host = await getSyncHost() 9 | // console.log(host) 10 | if (!host) { 11 | updateSetting({ 'sync.enable': false }) 12 | return 13 | } 14 | void connectServer(host) 15 | } 16 | -------------------------------------------------------------------------------- /src/core/player/playStatus.ts: -------------------------------------------------------------------------------- 1 | import playerActions from '@/store/player/action' 2 | import playerState from '@/store/player/state' 3 | 4 | export const setIsPlay = (val: boolean) => { 5 | if (playerState.isPlay == val) return 6 | playerActions.setIsPlay(val) 7 | } 8 | 9 | export const setStatusText = (val: string) => { 10 | if (playerState.statusText == val) return 11 | playerActions.setStatusText(val) 12 | } 13 | -------------------------------------------------------------------------------- /src/core/player/playedList.ts: -------------------------------------------------------------------------------- 1 | import playerActions from '@/store/player/action' 2 | 3 | /** 4 | * 将歌曲添加到已播放列表 5 | * @param playMusicInfo playMusicInfo对象 6 | */ 7 | export const addPlayedList = (playMusicInfo: LX.Player.PlayMusicInfo) => { 8 | playerActions.addPlayedList(playMusicInfo) 9 | } 10 | /** 11 | * 将歌曲从已播放列表移除 12 | * @param index 歌曲位置 13 | */ 14 | export const removePlayedList = (index: number) => { 15 | playerActions.removePlayedList(index) 16 | } 17 | /** 18 | * 清空已播放列表 19 | */ 20 | export const clearPlayedList = () => { 21 | playerActions.clearPlayedList() 22 | } 23 | -------------------------------------------------------------------------------- /src/core/player/progress.ts: -------------------------------------------------------------------------------- 1 | import playerActions from '@/store/player/action' 2 | 3 | export const setNowPlayTime = (time: number) => { 4 | playerActions.setNowPlayTime(time) 5 | } 6 | 7 | export const setMaxplayTime = (time: number) => { 8 | playerActions.setMaxplayTime(time) 9 | } 10 | 11 | export const setProgress = (currentTime: number, totalTime: number) => { 12 | playerActions.setProgress(currentTime, totalTime) 13 | } 14 | -------------------------------------------------------------------------------- /src/core/player/tempPlayList.ts: -------------------------------------------------------------------------------- 1 | import playerActions from '@/store/player/action' 2 | import playerState from '@/store/player/state' 3 | import { playNext } from './player' 4 | 5 | /** 6 | * 添加歌曲到稍后播放列表 7 | * @param list 歌曲列表 8 | */ 9 | export const addTempPlayList = (list: LX.Player.TempPlayListItem[]) => { 10 | playerActions.addTempPlayList(list) 11 | if (!playerState.playMusicInfo.musicInfo) void playNext() 12 | } 13 | /** 14 | * 从稍后播放列表移除歌曲 15 | * @param index 歌曲位置 16 | */ 17 | export const removeTempPlayList = (index: number) => { 18 | playerActions.removeTempPlayList(index) 19 | } 20 | /** 21 | * 清空稍后播放列表 22 | */ 23 | export const clearTempPlayeList = () => { 24 | playerActions.clearTempPlayeList() 25 | } 26 | -------------------------------------------------------------------------------- /src/core/theme.ts: -------------------------------------------------------------------------------- 1 | import themeActions from '@/store/theme/action' 2 | import { getTheme } from '@/theme/themes' 3 | import { updateSetting } from './common' 4 | import themeState from '@/store/theme/state' 5 | 6 | export const setShouldUseDarkColors = (shouldUseDarkColors: boolean) => { 7 | themeActions.setShouldUseDarkColors(shouldUseDarkColors) 8 | } 9 | 10 | export const applyTheme = (theme: LX.Theme) => { 11 | themeActions.setTheme(theme) 12 | } 13 | 14 | export const setTheme = (id: string) => { 15 | updateSetting({ 'theme.id': id }) 16 | void getTheme().then((theme) => { 17 | if (theme.id == themeState.theme.id) return 18 | applyTheme(theme) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/lang/Readme.md: -------------------------------------------------------------------------------- 1 | 新增语言时创建的语言文件夹需要与以下列表对应: 2 | 3 | - `ar_sa` - Arabic Saudi Arabia 4 | - `cs_cz` - Czech Czech Republic 5 | - `da_dk` - Danish Denmark 6 | - `de_de` - German Germany 7 | - `el_gr` - Modern Greek Greece 8 | - `en_au` - English Australia 9 | - `en_gb` - English United Kingdom 10 | - `en_ie` - English Ireland 11 | - `en_us` - English United States 12 | - `en_za` - English South Africa 13 | - `es_es` - Spanish Spain 14 | - `es_mx` - Spanish Mexico 15 | - `fi_fi` - Finnish Finland 16 | - `fr_ca` - French Canada 17 | - `fr_fr` - French France 18 | - `he_il` - Hebrew Israel 19 | - `hi_in` - Hindi India 20 | - `hu_hu` - Hungarian Hungary 21 | - `id_id` - Indonesian Indonesia 22 | - `it_it` - Italian Italy 23 | - `ja_jp` - Japanese Japan 24 | - `ko_kr` - Korean Republic of Korea 25 | - `nl_be` - Dutch Belgium 26 | - `nl_nl` - Dutch Netherlands 27 | - `no_no` - Norwegian Norway 28 | - `pl_pl` - Polish Poland 29 | - `pt_br` - Portuguese Brazil 30 | - `pt_pt` - Portuguese Portugal 31 | - `ro_ro` - Romanian Romania 32 | - `ru_ru` - Russian Russian Federation 33 | - `sk_sk` - Slovak Slovakia 34 | - `sv_se` - Swedish Sweden 35 | - `th_th` - Thai Thailand 36 | - `tr_tr` - Turkish Turkey 37 | - `zh_cn` - Chinese China 38 | - `zh_hk` - Chinese Hong Kong 39 | - `zh_tw` - Chinese Taiwan 40 | -------------------------------------------------------------------------------- /src/lang/index.ts: -------------------------------------------------------------------------------- 1 | import zh_cn from './zh-cn.json' 2 | import zh_tw from './zh-tw.json' 3 | import en_us from './en-us.json' 4 | 5 | type Message = 6 | | Record 7 | | Record 8 | | Record 9 | 10 | const langs = [ 11 | { 12 | name: '简体中文', 13 | locale: 'zh_cn', 14 | // alternate: 'zh-hans', 15 | country: 'cn', 16 | fallback: true, 17 | message: zh_cn, 18 | }, 19 | { 20 | name: '繁體中文', 21 | locale: 'zh_tw', 22 | // alternate: 'zh-hant', 23 | country: 'cn', 24 | message: zh_tw, 25 | }, 26 | { 27 | name: 'English', 28 | locale: 'en_us', 29 | country: 'us', 30 | message: en_us, 31 | }, 32 | ] as const 33 | 34 | const langList: Array<{ 35 | name: string 36 | locale: (typeof langs)[number]['locale'] 37 | // alternate?: string 38 | }> = [] 39 | type Messages = Record<(typeof langs)[number]['locale'], Message> 40 | 41 | // @ts-expect-error 42 | const messages: Messages = {} 43 | 44 | langs.forEach((item) => { 45 | langList.push({ 46 | name: item.name, 47 | locale: item.locale, 48 | // alternate: item.alternate, 49 | }) 50 | messages[item.locale] = item.message 51 | }) 52 | 53 | export { langList, messages } 54 | 55 | export type { Messages, Message } 56 | 57 | export * from './i18n' 58 | -------------------------------------------------------------------------------- /src/navigation/event.ts: -------------------------------------------------------------------------------- 1 | import { type EmitterSubscription } from 'react-native' 2 | import { Navigation } from 'react-native-navigation' 3 | 4 | export const onModalDismissed = (id: string, handler: () => void) => { 5 | let modalDismissedListener: EmitterSubscription | null = 6 | Navigation.events().registerModalDismissedListener(({ componentId, modalsDismissed }) => { 7 | if (componentId != id || !modalDismissedListener) return 8 | handler() 9 | modalDismissedListener.remove() 10 | modalDismissedListener = null 11 | }) 12 | return () => { 13 | if (!modalDismissedListener) return 14 | modalDismissedListener.remove() 15 | modalDismissedListener = null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/navigation/index.ts: -------------------------------------------------------------------------------- 1 | import { Navigation } from 'react-native-navigation' 2 | import * as screenNames from './screenNames' 3 | import * as navigations from './navigation' 4 | 5 | import registerScreens from './registerScreens' 6 | import { removeComponentId } from '@/core/common' 7 | import { onAppLaunched } from './regLaunchedEvent' 8 | 9 | let unRegisterEvent: ReturnType< 10 | ReturnType['registerScreenPoppedListener'] 11 | > 12 | 13 | const init = (callback: () => void | Promise) => { 14 | // Register all screens on launch 15 | registerScreens() 16 | 17 | if (unRegisterEvent) unRegisterEvent.remove() 18 | 19 | Navigation.setDefaultOptions({ 20 | // animations: { 21 | // setRoot: { 22 | // waitForRender: true, 23 | // }, 24 | // }, 25 | }) 26 | unRegisterEvent = Navigation.events().registerScreenPoppedListener(({ componentId }) => { 27 | removeComponentId(componentId) 28 | }) 29 | onAppLaunched(() => { 30 | console.log('Register app launched listener') 31 | void callback() 32 | }) 33 | } 34 | 35 | export * from './utils' 36 | export * from './event' 37 | export * from './hooks' 38 | 39 | export { init, screenNames, navigations } 40 | -------------------------------------------------------------------------------- /src/navigation/regLaunchedEvent.ts: -------------------------------------------------------------------------------- 1 | import { Navigation } from 'react-native-navigation' 2 | 3 | let launched = false 4 | const handlers: Array<() => void> = [] 5 | 6 | export const listenLaunchEvent = () => { 7 | Navigation.events().registerAppLaunchedListener(() => { 8 | // console.log('Register app launched listener', launched) 9 | launched = true 10 | setImmediate(() => { 11 | for (const handler of handlers) handler() 12 | }) 13 | }) 14 | } 15 | 16 | export const onAppLaunched = (handler: () => void) => { 17 | handlers.push(handler) 18 | if (launched) { 19 | setImmediate(() => { 20 | handler() 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/navigation/screenNames.ts: -------------------------------------------------------------------------------- 1 | export const HOME_SCREEN = 'lxm.HomeScreen' 2 | export const PLAY_DETAIL_SCREEN = 'lxm.PlayDetailScreen' 3 | export const SONGLIST_DETAIL_SCREEN = 'lxm.SonglistDetailScreen' 4 | export const COMMENT_SCREEN = 'lxm.CommentScreen' 5 | export const VERSION_MODAL = 'lxm.VersionModal' 6 | export const PACT_MODAL = 'lxm.PactModal' 7 | export const SYNC_MODE_MODAL = 'lxm.SyncModeModal' 8 | // export const SETTING_SCREEN = 'lxm.SettingScreen' 9 | // export const TOAST_SCREEN = 'lxm.ToastScreen' 10 | -------------------------------------------------------------------------------- /src/plugins/sync/client/modules/dislike/index.ts: -------------------------------------------------------------------------------- 1 | export { default as handler } from './handler' 2 | 3 | export * from './localEvent' 4 | -------------------------------------------------------------------------------- /src/plugins/sync/client/modules/dislike/localEvent.ts: -------------------------------------------------------------------------------- 1 | import { SYNC_CLOSE_CODE } from '@/plugins/sync/constants' 2 | import { registerDislikeActionEvent } from '../../../dislikeEvent' 3 | 4 | let unregisterLocalListAction: (() => void) | null 5 | 6 | export const registerEvent = (socket: LX.Sync.Socket) => { 7 | // socket = _socket 8 | // socket.onClose(() => { 9 | // unregisterLocalListAction?.() 10 | // unregisterLocalListAction = null 11 | // }) 12 | unregisterEvent() 13 | unregisterLocalListAction = registerDislikeActionEvent((action) => { 14 | if (!socket.moduleReadys?.dislike) return 15 | void socket.remoteQueueDislike.onDislikeSyncAction(action).catch((err) => { 16 | // TODO send status 17 | socket.moduleReadys.dislike = false 18 | socket.close(SYNC_CLOSE_CODE.failed) 19 | console.log(err.message) 20 | }) 21 | }) 22 | } 23 | 24 | export const unregisterEvent = () => { 25 | unregisterLocalListAction?.() 26 | unregisterLocalListAction = null 27 | } 28 | -------------------------------------------------------------------------------- /src/plugins/sync/client/modules/index.ts: -------------------------------------------------------------------------------- 1 | import * as list from './list' 2 | import * as dislike from './dislike' 3 | // export * as theme from './theme' 4 | 5 | export const callObj = Object.assign({}, list.handler, dislike.handler) 6 | 7 | export const modules = { 8 | list, 9 | dislike, 10 | } 11 | 12 | export const featureVersion = { 13 | list: 1, 14 | dislike: 1, 15 | } as const 16 | -------------------------------------------------------------------------------- /src/plugins/sync/client/modules/list/index.ts: -------------------------------------------------------------------------------- 1 | export { default as handler } from './handler' 2 | 3 | export * from './localEvent' 4 | -------------------------------------------------------------------------------- /src/plugins/sync/client/modules/list/localEvent.ts: -------------------------------------------------------------------------------- 1 | import { SYNC_CLOSE_CODE } from '@/plugins/sync/constants' 2 | import { registerListActionEvent } from '../../../listEvent' 3 | 4 | let unregisterLocalListAction: (() => void) | null 5 | 6 | export const registerEvent = (socket: LX.Sync.Socket) => { 7 | // socket = _socket 8 | // socket.onClose(() => { 9 | // unregisterLocalListAction?.() 10 | // unregisterLocalListAction = null 11 | // }) 12 | unregisterEvent() 13 | unregisterLocalListAction = registerListActionEvent((action) => { 14 | if (!socket.moduleReadys?.list) return 15 | void socket.remoteQueueList.onListSyncAction(action).catch((err) => { 16 | // TODO send status 17 | socket.moduleReadys.list = false 18 | socket.close(SYNC_CLOSE_CODE.failed) 19 | console.log(err.message) 20 | }) 21 | }) 22 | } 23 | 24 | export const unregisterEvent = () => { 25 | unregisterLocalListAction?.() 26 | unregisterLocalListAction = null 27 | } 28 | -------------------------------------------------------------------------------- /src/plugins/sync/client/sync/handler.ts: -------------------------------------------------------------------------------- 1 | // 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象 2 | // import { getUserSpace } from '@/user' 3 | // import { modules } from '../modules' 4 | 5 | import { featureVersion } from '../modules' 6 | 7 | const handler: Omit, 'finished'> = { 8 | async getEnabledFeatures(socket, serverType, supportedFeatures) { 9 | // const userSpace = getUserSpace(socket.userInfo.name) 10 | const features: LX.Sync.EnabledFeatures = {} 11 | switch (serverType) { 12 | case 'server': 13 | if (featureVersion.list == supportedFeatures.list) { 14 | features.list = { skipSnapshot: false } 15 | } 16 | if (featureVersion.dislike == supportedFeatures.dislike) { 17 | features.dislike = { skipSnapshot: false } 18 | } 19 | return features 20 | case 'desktop-app': 21 | default: 22 | if (featureVersion.list == supportedFeatures.list) { 23 | features.list = { skipSnapshot: false } 24 | } 25 | if (featureVersion.dislike == supportedFeatures.dislike) { 26 | features.dislike = { skipSnapshot: false } 27 | } 28 | return features 29 | } 30 | }, 31 | } 32 | 33 | export default handler 34 | -------------------------------------------------------------------------------- /src/plugins/sync/client/sync/index.ts: -------------------------------------------------------------------------------- 1 | import handler from './handler' 2 | import { callObj as _callObj } from '../modules' 3 | export { modules } from '../modules' 4 | 5 | export const callObj = { 6 | ...handler, 7 | ..._callObj, 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/sync/data.ts: -------------------------------------------------------------------------------- 1 | export { 2 | getSyncAuthKey, 3 | setSyncAuthKey, 4 | getSyncHost, 5 | setSyncHost, 6 | getSyncHostHistory, 7 | addSyncHostHistory, 8 | removeSyncHostHistory, 9 | } from '@/utils/data' 10 | -------------------------------------------------------------------------------- /src/plugins/sync/index.ts: -------------------------------------------------------------------------------- 1 | // import Event from './event/event' 2 | 3 | export { connectServer, disconnectServer, getStatus } from './client' 4 | -------------------------------------------------------------------------------- /src/plugins/sync/log.ts: -------------------------------------------------------------------------------- 1 | import { log as writeLog } from '@/utils/log' 2 | 3 | export default { 4 | r_info(...params: any[]) { 5 | writeLog.info(...params) 6 | }, 7 | r_warn(...params: any[]) { 8 | writeLog.warn(...params) 9 | }, 10 | r_error(...params: any[]) { 11 | writeLog.error(...params) 12 | }, 13 | info(...params: any[]) { 14 | if (global.lx.isEnableSyncLog) writeLog.info(...params) 15 | }, 16 | warn(...params: any[]) { 17 | if (global.lx.isEnableSyncLog) writeLog.warn(...params) 18 | }, 19 | error(...params: any[]) { 20 | if (global.lx.isEnableSyncLog) writeLog.error(...params) 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/resources/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/resources/images/defaultUser.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/defaultUser.jpg -------------------------------------------------------------------------------- /src/resources/images/notification.hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification.hdpi.png -------------------------------------------------------------------------------- /src/resources/images/notification.ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification.ldpi.png -------------------------------------------------------------------------------- /src/resources/images/notification.mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification.mdpi.png -------------------------------------------------------------------------------- /src/resources/images/notification.xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification.xhdpi.png -------------------------------------------------------------------------------- /src/resources/images/notification2.hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification2.hdpi.png -------------------------------------------------------------------------------- /src/resources/images/notification2.ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification2.ldpi.png -------------------------------------------------------------------------------- /src/resources/images/notification2.mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification2.mdpi.png -------------------------------------------------------------------------------- /src/resources/images/notification2.xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/images/notification2.xhdpi.png -------------------------------------------------------------------------------- /src/resources/medias/Silence02s.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ikunshare/ikun-music-mobile/367c64a36b3154f4954d7a3f60a15bda38263bb8/src/resources/medias/Silence02s.mp3 -------------------------------------------------------------------------------- /src/screens/Home/Horizontal/Main.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useState } from 'react' 2 | import Search from '../Views/Search' 3 | import SongList from '../Views/SongList' 4 | import Mylist from '../Views/Mylist' 5 | import Leaderboard from '../Views/Leaderboard' 6 | import Setting from '../Views/Setting' 7 | import commonState, { type InitState as CommonState } from '@/store/common/state' 8 | 9 | const Main = () => { 10 | const [id, setId] = useState(commonState.navActiveId) 11 | 12 | useEffect(() => { 13 | const handleUpdate = (id: CommonState['navActiveId']) => { 14 | requestAnimationFrame(() => { 15 | setId(id) 16 | }) 17 | } 18 | global.state_event.on('navActiveIdUpdated', handleUpdate) 19 | return () => { 20 | global.state_event.off('navActiveIdUpdated', handleUpdate) 21 | } 22 | }, []) 23 | 24 | const component = useMemo(() => { 25 | switch (id) { 26 | case 'nav_songlist': 27 | return 28 | case 'nav_top': 29 | return 30 | case 'nav_love': 31 | return 32 | case 'nav_setting': 33 | return 34 | case 'nav_search': 35 | default: 36 | return 37 | } 38 | }, [id]) 39 | 40 | return component 41 | } 42 | 43 | export default Main 44 | -------------------------------------------------------------------------------- /src/screens/Home/Horizontal/index.tsx: -------------------------------------------------------------------------------- 1 | import { View } from 'react-native' 2 | import Aside from './Aside' 3 | import PlayerBar from '@/components/player/PlayerBar' 4 | import StatusBar from '@/components/common/StatusBar' 5 | import Header from './Header' 6 | import Main from './Main' 7 | import { createStyle } from '@/utils/tools' 8 | 9 | const styles = createStyle({ 10 | container: { 11 | flex: 1, 12 | flexDirection: 'row', 13 | }, 14 | content: { 15 | flex: 1, 16 | overflow: 'hidden', 17 | }, 18 | }) 19 | 20 | export default () => { 21 | return ( 22 | <> 23 | 24 | 25 |