├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── schemas │ └── com.paperpig.maimaidata.db.AppDataBase │ │ └── 1.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── paperpig │ │ └── maimaidata │ │ ├── MaimaiDataApplication.kt │ │ ├── crawler │ │ ├── CrawlerCaller.kt │ │ ├── SimpleCookieJar.java │ │ ├── WechatCrawler.java │ │ └── WechatCrawlerListener.kt │ │ ├── db │ │ ├── AppDataBase.kt │ │ ├── dao │ │ │ ├── AliasDao.kt │ │ │ ├── ChartDao.kt │ │ │ ├── ChartStatsDao.kt │ │ │ ├── RecordDao.kt │ │ │ ├── SongDao.kt │ │ │ └── SongWithChartsDao.kt │ │ └── entity │ │ │ ├── AliasEntity.kt │ │ │ ├── ChartEntity.kt │ │ │ ├── ChartStatsEntity.kt │ │ │ ├── RecordEntity.kt │ │ │ ├── SongDataEntity.kt │ │ │ └── SongWithChartsEntity.kt │ │ ├── glide │ │ └── MyGlideModule.kt │ │ ├── model │ │ ├── AppUpdateModel.kt │ │ ├── ChartsResponse.kt │ │ ├── DifficultyType.kt │ │ ├── DsSongData.kt │ │ ├── MaxNotesStats.kt │ │ ├── Rating.kt │ │ ├── ResponseErrorBody.kt │ │ ├── SongData.kt │ │ └── Version.kt │ │ ├── network │ │ ├── MaimaiDataClient.kt │ │ ├── MaimaiDataRequests.kt │ │ ├── MaimaiDataService.kt │ │ ├── MaimaiDataTransformer.kt │ │ ├── OkHttpStreamFetcher.kt │ │ ├── OkHttpUrlLoader.kt │ │ ├── UnsafeOkHttpClient.kt │ │ ├── server │ │ │ ├── HttpRedirectServer.java │ │ │ ├── HttpServer.java │ │ │ └── HttpServerService.java │ │ └── vpn │ │ │ ├── core │ │ │ ├── Constant.java │ │ │ ├── DnsProxy.java │ │ │ ├── HttpHostHeaderParser.java │ │ │ ├── LocalVpnService.java │ │ │ ├── NatSession.java │ │ │ ├── NatSessionManager.java │ │ │ ├── ProxyConfig.java │ │ │ ├── TcpProxyServer.java │ │ │ └── TunnelFactory.java │ │ │ ├── dns │ │ │ ├── DnsFlags.java │ │ │ ├── DnsHeader.java │ │ │ ├── DnsPacket.java │ │ │ ├── Question.java │ │ │ ├── Resource.java │ │ │ └── ResourcePointer.java │ │ │ ├── tcpip │ │ │ ├── CommonMethods.java │ │ │ ├── IPHeader.java │ │ │ ├── TCPHeader.java │ │ │ └── UDPHeader.java │ │ │ └── tunnel │ │ │ ├── Config.java │ │ │ ├── HttpCapturerTunnel.java │ │ │ ├── RawTunnel.java │ │ │ ├── Tunnel.java │ │ │ └── httpconnect │ │ │ └── HttpConnectConfig.java │ │ ├── repository │ │ ├── AliasRepository.kt │ │ ├── ChartRepository.kt │ │ ├── ChartStatsRepository.kt │ │ ├── RecordRepository.kt │ │ ├── SongDataRepository.kt │ │ └── SongWithChartRepository.kt │ │ ├── ui │ │ ├── BaseFragment.kt │ │ ├── MainActivity.kt │ │ ├── about │ │ │ ├── AboutFragment.kt │ │ │ ├── SettingsActivity.kt │ │ │ └── SettingsFragment.kt │ │ ├── checklist │ │ │ ├── LevelArrayAdapter.kt │ │ │ ├── LevelCheckActivity.kt │ │ │ ├── LevelCheckAdapter.kt │ │ │ ├── VersionArrayAdapter.kt │ │ │ ├── VersionCheckActivity.kt │ │ │ └── VersionCheckAdapter.kt │ │ ├── finaletodx │ │ │ └── FinaleToDxActivity.kt │ │ ├── maimaidxprober │ │ │ ├── AccountAdapter.kt │ │ │ ├── AccountListBottomSheet.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── ProberActivity.kt │ │ │ ├── ProberVersionAdapter.kt │ │ │ └── RecordAdapter.kt │ │ ├── rating │ │ │ ├── ProberUpdateDialog.kt │ │ │ ├── RatingFragment.kt │ │ │ └── RatingResultAdapter.kt │ │ ├── songdetail │ │ │ ├── PinchImageActivity.kt │ │ │ ├── SongDetailActivity.kt │ │ │ └── SongLevelFragment.kt │ │ └── songlist │ │ │ ├── SongListAdapter.kt │ │ │ └── SongListFragment.kt │ │ ├── utils │ │ ├── Constants.kt │ │ ├── ConvertUtils.kt │ │ ├── CreateBest50.kt │ │ ├── Extensions.kt │ │ ├── JsonConvertToDb.kt │ │ ├── PermissionHelper.kt │ │ ├── PictureUtils.kt │ │ ├── SpUtil.kt │ │ └── WindowsUtils.kt │ │ └── widgets │ │ ├── AnimationHelper.kt │ │ ├── ChartBarView.kt │ │ ├── ClearEditText.kt │ │ ├── PinchImageView.java │ │ ├── SearchLayout.kt │ │ ├── Settings.kt │ │ ├── ShootingStarView.kt │ │ └── TilingImageView.kt │ └── res │ ├── drawable-anydpi │ ├── ic_menu.xml │ └── ic_search.xml │ ├── drawable-hdpi │ ├── ic_menu.png │ └── ic_search.png │ ├── drawable-mdpi │ ├── ic_menu.png │ └── ic_search.png │ ├── drawable-xhdpi │ ├── ic_menu.png │ └── ic_search.png │ ├── drawable-xxhdpi │ ├── ic_delete.png │ ├── ic_deluxe.png │ ├── ic_list.png │ ├── ic_menu.png │ ├── ic_rating.png │ ├── ic_search.png │ ├── ic_standard.png │ ├── ic_tips.png │ ├── ic_transform.png │ ├── icon_on.png │ ├── maimai.png │ ├── maimai_finale.png │ ├── maimai_green.png │ ├── maimai_green_plus.png │ ├── maimai_milk.png │ ├── maimai_milk_plus.png │ ├── maimai_murasaki.png │ ├── maimai_murasaki_plus.png │ ├── maimai_orange.png │ ├── maimai_orange_plus.png │ ├── maimai_pink.png │ ├── maimai_pink_plus.png │ ├── maimai_plus.png │ ├── maimaidx.png │ ├── maimaidx_2021.png │ ├── maimaidx_2022.png │ ├── maimaidx_2023.png │ ├── maimaidx_2024.png │ ├── maimaidx_buddies.png │ ├── maimaidx_buddies_plus.png │ ├── maimaidx_cn.png │ ├── maimaidx_festival.png │ ├── maimaidx_festival_plus.png │ ├── maimaidx_plus.png │ ├── maimaidx_splash.png │ ├── maimaidx_splash_plus.png │ ├── maimaidx_universe.png │ ├── maimaidx_universe_plus.png │ ├── mmd_chara_sp.png │ ├── mmd_checklist_top_bg.9.png │ ├── mmd_cover_save.png │ ├── mmd_cover_share.png │ ├── mmd_delete_search_history.png │ ├── mmd_favorite_checked.png │ ├── mmd_laundry.png │ ├── mmd_main_bg_back_center.png │ ├── mmd_main_bg_back_left.png │ ├── mmd_main_bg_back_right.png │ ├── mmd_main_bg_front_center.png │ ├── mmd_main_bg_front_left.png │ ├── mmd_main_bg_front_right.png │ ├── mmd_main_bg_moon.png │ ├── mmd_main_bg_rainbow.png │ ├── mmd_main_bg_rainbow_bottom.png │ ├── mmd_player_best50.png │ ├── mmd_player_name_box.png │ ├── mmd_player_num_drating_0.png │ ├── mmd_player_num_drating_1.png │ ├── mmd_player_num_drating_2.png │ ├── mmd_player_num_drating_3.png │ ├── mmd_player_num_drating_4.png │ ├── mmd_player_num_drating_5.png │ ├── mmd_player_num_drating_6.png │ ├── mmd_player_num_drating_7.png │ ├── mmd_player_num_drating_8.png │ ├── mmd_player_num_drating_9.png │ ├── mmd_player_rating_box.png │ ├── mmd_player_rtsong_a.png │ ├── mmd_player_rtsong_aa.png │ ├── mmd_player_rtsong_aaa.png │ ├── mmd_player_rtsong_ap.png │ ├── mmd_player_rtsong_app.png │ ├── mmd_player_rtsong_b.png │ ├── mmd_player_rtsong_bb.png │ ├── mmd_player_rtsong_bbb.png │ ├── mmd_player_rtsong_c.png │ ├── mmd_player_rtsong_d.png │ ├── mmd_player_rtsong_diff_adv.9.png │ ├── mmd_player_rtsong_diff_bsc.9.png │ ├── mmd_player_rtsong_diff_exp.9.png │ ├── mmd_player_rtsong_diff_mst.9.png │ ├── mmd_player_rtsong_diff_rem.9.png │ ├── mmd_player_rtsong_fc.png │ ├── mmd_player_rtsong_fcp.png │ ├── mmd_player_rtsong_fs.png │ ├── mmd_player_rtsong_fsd.png │ ├── mmd_player_rtsong_fsdp.png │ ├── mmd_player_rtsong_fsp.png │ ├── mmd_player_rtsong_icon_dx.png │ ├── mmd_player_rtsong_icon_standard.png │ ├── mmd_player_rtsong_level_bg.png │ ├── mmd_player_rtsong_rating_icon.png │ ├── mmd_player_rtsong_s.png │ ├── mmd_player_rtsong_sp.png │ ├── mmd_player_rtsong_ss.png │ ├── mmd_player_rtsong_ssp.png │ ├── mmd_player_rtsong_sss.png │ ├── mmd_player_rtsong_sssp.png │ ├── mmd_player_rtsong_stub.png │ ├── mmd_rating_board_adv.png │ ├── mmd_rating_board_bsc.png │ ├── mmd_rating_board_exp.png │ ├── mmd_rating_board_mas.png │ ├── mmd_rating_board_rem.png │ ├── mmd_rating_diff_advanced.png │ ├── mmd_rating_diff_basic.png │ ├── mmd_rating_diff_expert.png │ ├── mmd_rating_diff_master.png │ ├── mmd_rating_diff_remaster.png │ ├── mmd_rating_plate_blue.png │ ├── mmd_rating_plate_bronze.png │ ├── mmd_rating_plate_gold.png │ ├── mmd_rating_plate_green.png │ ├── mmd_rating_plate_normal.png │ ├── mmd_rating_plate_orange.png │ ├── mmd_rating_plate_platinum.png │ ├── mmd_rating_plate_purple.png │ ├── mmd_rating_plate_rainbow.png │ ├── mmd_rating_plate_red.png │ ├── mmd_rating_plate_silver.png │ ├── mmd_record_divider.9.png │ ├── mmd_shooting_star_head.png │ ├── mmd_shooting_star_tail.png │ ├── mmd_song_jacket_placeholder.png │ └── mmd_song_utage_party.png │ ├── drawable │ ├── baseline_percent_24.xml │ ├── divider.xml │ ├── eye_closed.png │ ├── eye_open.png │ ├── level_advanced_bg.xml │ ├── level_basic_bg.xml │ ├── level_expert_bg.xml │ ├── level_master_bg.xml │ ├── level_remaster_bg.xml │ ├── level_utage_bg.xml │ ├── mmd_checkbox_genre_chuni_checked_bg.xml │ ├── mmd_checkbox_genre_chuni_selector.xml │ ├── mmd_checkbox_genre_maimai_checked_bg.xml │ ├── mmd_checkbox_genre_maimai_selector.xml │ ├── mmd_checkbox_genre_nico_checked_bg.xml │ ├── mmd_checkbox_genre_nico_selector.xml │ ├── mmd_checkbox_genre_pop_checked_bg.xml │ ├── mmd_checkbox_genre_pop_selector.xml │ ├── mmd_checkbox_genre_touhou_checked_bg.xml │ ├── mmd_checkbox_genre_touhou_selector.xml │ ├── mmd_checkbox_genre_utage_checked_bg.xml │ ├── mmd_checkbox_genre_utage_selector.xml │ ├── mmd_checkbox_genre_variety_checked_bg.xml │ ├── mmd_checkbox_genre_variety_selector.xml │ ├── mmd_checkbox_unchecked_bg.xml │ ├── mmd_checkbox_version_checked_bg.xml │ ├── mmd_checkbox_version_selector.xml │ ├── mmd_main_bg_aurora.png │ ├── mmd_main_bg_gradient.xml │ ├── mmd_main_bg_pattern.png │ ├── mmd_main_bg_shines.png │ ├── mmd_main_bg_star_white.png │ ├── mmd_main_bg_star_yellow.png │ ├── mmd_player_rtsong_bg.xml │ ├── mmd_player_rtsong_container_bg.xml │ ├── mmd_player_rtsong_jacket_bg.xml │ ├── mmd_player_rtsong_other_info_bg.xml │ ├── mmd_player_rtsong_title_bg.xml │ ├── mmd_search_lens.xml │ ├── mmd_song_alias_info_bg.xml │ ├── mmd_textview_search_history_bg.xml │ ├── nav_bar_text_color_selector.xml │ ├── prober_version_bg.xml │ ├── search_bar_button_bg.xml │ ├── search_bar_left_bg.xml │ ├── search_bar_right_bg.xml │ ├── search_checkbox_checked_bg.xml │ ├── song_list_genre_bg.xml │ ├── song_list_info_bg.xml │ ├── song_note_achievement_bg.xml │ └── song_note_bg.xml │ ├── layout-v28 │ └── item_search_history.xml │ ├── layout │ ├── activity_finale_to_dx.xml │ ├── activity_level_check.xml │ ├── activity_login.xml │ ├── activity_main.xml │ ├── activity_pinch_image.xml │ ├── activity_prober.xml │ ├── activity_settings.xml │ ├── activity_song_detail.xml │ ├── activity_version_check.xml │ ├── dialog_prober_update.xml │ ├── fragment_account_list_bottom_sheet.xml │ ├── fragment_rating.xml │ ├── fragment_song_level.xml │ ├── fragment_song_list.xml │ ├── item_check_header.xml │ ├── item_dots.xml │ ├── item_level_header.xml │ ├── item_normal_song.xml │ ├── item_rating_reuslt.xml │ ├── item_search_history.xml │ ├── item_song_check.xml │ ├── item_spinner_level.xml │ ├── item_spinner_version.xml │ ├── item_utage_song.xml │ ├── layout_song_search.xml │ ├── mmd_main_style_bg_layout.xml │ ├── mmd_player_rtsong_divider_layout.xml │ ├── mmd_player_rtsong_layout.xml │ ├── search_bar.xml │ └── title.xml │ ├── menu │ ├── about_menu.xml │ ├── main_menu.xml │ ├── nav_menu.xml │ └── share_menu.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values │ ├── arrays.xml │ ├── blacklist.xml │ ├── colors.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── abot_preferences.xml │ ├── provider_paths.xml │ └── settings_preferences.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | /.idea/ 16 | /keystore.jks 17 | .kotlin/sessions/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaimaiData 2 | 3 | MaimaiData是一款为舞萌DX玩家开发的Android App 4 | 5 | 包含歌曲信息检索,谱面信息查看,rating计算,分数查看等功能 6 | 7 | ## 最近更新 8 | ### v2.5.4 Latest 9 | 1. 修复打开版本进度崩溃问题 10 | 2. 增加歌曲id显示 11 | 3. 增加谱师名复制 12 | 4. 设置中可开启谱师名搜索和B50图片自定义昵称 13 | 14 | ### v2.5.3 15 | 1. 进度表记忆上次选择的版本和等级 16 | 2. 歌曲详情界面显示国服添加版本和谱面类型 17 | 3. 歌曲详情界面长按标题和别名可以进行复制 18 | 4. 歌曲详情界面点击歌曲封面可查看大图 19 | 20 | ### v2.5.0 21 | 1. 更新主题色为prism 22 | 2. 优化搜索布局,支持了歌曲别名和定数等级的搜索 23 | 3. 增加了歌曲别名、拟合定数的显示 24 | 4. 增加了曲目列表右侧的快速滚动条 25 | 5. 增加了账号切换功能 26 | 6. 谱面note分布改为图表显示 27 | 28 | ## 已知问题 29 | 标准谱追加的DX谱、DX谱追加的标准谱的日服添加版本缺少可判断的字段,暂时显示为前者添加版本 30 | 31 | ## 感谢 32 | 感谢[Diving-Fish](https://github.com/Diving-Fish/maimaidx-prober)提供的谱面数据 33 | 34 | 感谢[Xray Bot](https://download.fanyu.site/maimai/alias.json)提供的别名库 35 | 36 | ## 许可证 37 | 本项目基于 Apache License 2.0 开源许可证发布。您可以在遵守许可证条款的前提下自由使用、修改和分发本软件。 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/androidTest/ 3 | /src/test/ 4 | /release/ 5 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 12 | 14 | 15 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/MaimaiDataApplication.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.paperpig.maimaidata.db.AppDataBase 6 | import com.paperpig.maimaidata.model.MaxNotesStats 7 | import com.paperpig.maimaidata.network.MaimaiDataClient 8 | import com.paperpig.maimaidata.utils.SpUtil 9 | import com.paperpig.maimaidata.widgets.Settings 10 | 11 | /** 12 | * @author BBS 13 | * @since 2021/5/13 14 | */ 15 | class MaimaiDataApplication : Application() { 16 | companion object { 17 | lateinit var instance: MaimaiDataApplication 18 | } 19 | 20 | var maxNotesStats: MaxNotesStats? = null 21 | 22 | override fun attachBaseContext(base: Context?) { 23 | super.attachBaseContext(base) 24 | instance = this 25 | MaimaiDataClient.instance.init() 26 | } 27 | 28 | override fun onCreate() { 29 | super.onCreate() 30 | 31 | Settings.init(this) 32 | 33 | SpUtil.init(this) 34 | 35 | AppDataBase.init(this) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/crawler/CrawlerCaller.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.crawler 2 | 3 | import com.paperpig.maimaidata.network.vpn.core.LocalVpnService 4 | import com.paperpig.maimaidata.utils.SpUtil 5 | import com.paperpig.maimaidata.widgets.Settings 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.launch 9 | import java.io.IOException 10 | 11 | object CrawlerCaller { 12 | private var listener: WechatCrawlerListener? = null 13 | 14 | fun getWechatAuthUrl(): String? { 15 | return try { 16 | val crawler = WechatCrawler() 17 | crawler.getWechatAuthUrl() 18 | } catch (error: IOException) { 19 | writeLog("获取微信登录url时出现错误:") 20 | onError(error) 21 | null 22 | } 23 | } 24 | 25 | @JvmStatic 26 | fun writeLog(text: String) { 27 | CoroutineScope(Dispatchers.Main).launch { 28 | listener?.onMessageReceived(text) 29 | } 30 | } 31 | 32 | @JvmStatic 33 | fun startAuth() { 34 | CoroutineScope(Dispatchers.Main).launch { 35 | listener?.onStartAuth() 36 | } 37 | } 38 | 39 | @JvmStatic 40 | fun finishUpdate() { 41 | CoroutineScope(Dispatchers.Main).launch { 42 | listener?.onFinishUpdate() 43 | } 44 | } 45 | 46 | @JvmStatic 47 | fun onError(e: Exception) { 48 | CoroutineScope(Dispatchers.Main).launch { 49 | listener?.onError(e) 50 | } 51 | } 52 | 53 | fun fetchData(authUrl: String) { 54 | CoroutineScope(Dispatchers.IO).launch { 55 | try { 56 | Thread.sleep(3000) 57 | LocalVpnService.IsRunning = false 58 | Thread.sleep(3000) 59 | } catch (e: InterruptedException) { 60 | onError(e) 61 | } 62 | try { 63 | val crawler = WechatCrawler() 64 | crawler.fetchAndUploadData( 65 | SpUtil.getUserName(), 66 | SpUtil.getPassword(), 67 | getDifficulties(), 68 | authUrl 69 | ) 70 | } catch (e: IOException) { 71 | onError(e) 72 | } 73 | } 74 | } 75 | 76 | fun setOnWechatCrawlerListener(listener: WechatCrawlerListener) { 77 | this.listener = listener 78 | } 79 | 80 | fun removeOnWechatCrawlerListener() { 81 | this.listener = null 82 | } 83 | 84 | private fun getDifficulties(): Set { 85 | return Settings.getUpdateDifficulty() 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/crawler/SimpleCookieJar.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.crawler; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import okhttp3.Cookie; 9 | import okhttp3.CookieJar; 10 | import okhttp3.HttpUrl; 11 | 12 | public class SimpleCookieJar implements CookieJar { 13 | private final Map> cookieStore = new HashMap>(); 14 | private final Object lock = new Object(); 15 | 16 | @Override 17 | public void saveFromResponse(HttpUrl httpUrl, List newCookies) { 18 | synchronized (lock) { 19 | HashMap map = new HashMap<>(); 20 | List oldCookies = cookieStore.get(httpUrl.host()); 21 | if (oldCookies != null) { 22 | for (Cookie cookie : oldCookies) { 23 | map.put(cookie.name(), cookie); 24 | } 25 | } 26 | // Override old cookie with same name 27 | if (newCookies != null) { 28 | for (Cookie cookie : newCookies) { 29 | map.put(cookie.name(), cookie); 30 | } 31 | } 32 | List mergedList = new ArrayList(); 33 | for (Map.Entry pair : map.entrySet()) { 34 | mergedList.add(pair.getValue()); 35 | } 36 | cookieStore.put(httpUrl.host(), mergedList); 37 | } 38 | } 39 | 40 | @Override 41 | public List loadForRequest(HttpUrl httpUrl) { 42 | synchronized (lock) { 43 | List cookies = cookieStore.get(httpUrl.host()); 44 | return cookies != null ? cookies : new ArrayList(); 45 | } 46 | } 47 | 48 | public void clearCookieStroe() { 49 | synchronized (lock) { 50 | this.cookieStore.clear(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/crawler/WechatCrawlerListener.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.crawler 2 | 3 | interface WechatCrawlerListener { 4 | fun onMessageReceived(logString: String) 5 | 6 | fun onStartAuth() 7 | 8 | fun onFinishUpdate() 9 | 10 | fun onError(e: Exception) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/AppDataBase.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.TypeConverters 8 | import androidx.sqlite.SQLiteConnection 9 | import com.paperpig.maimaidata.BuildConfig 10 | import com.paperpig.maimaidata.db.AppDataBase.Companion.DATABASE_VERSION 11 | import com.paperpig.maimaidata.db.dao.AliasDao 12 | import com.paperpig.maimaidata.db.dao.ChartDao 13 | import com.paperpig.maimaidata.db.dao.ChartStatsDao 14 | import com.paperpig.maimaidata.db.dao.RecordDao 15 | import com.paperpig.maimaidata.db.dao.SongDao 16 | import com.paperpig.maimaidata.db.dao.SongWithChartsDao 17 | import com.paperpig.maimaidata.db.entity.AliasEntity 18 | import com.paperpig.maimaidata.db.entity.ChartEntity 19 | import com.paperpig.maimaidata.db.entity.ChartStatsEntity 20 | import com.paperpig.maimaidata.db.entity.ListIntConverter 21 | import com.paperpig.maimaidata.db.entity.RecordEntity 22 | import com.paperpig.maimaidata.db.entity.SongDataEntity 23 | import com.paperpig.maimaidata.utils.SpUtil 24 | 25 | @Database( 26 | entities = [SongDataEntity::class, ChartEntity::class, AliasEntity::class, RecordEntity::class, ChartStatsEntity::class], 27 | version = DATABASE_VERSION 28 | ) 29 | @TypeConverters(ListIntConverter::class) 30 | abstract class AppDataBase : RoomDatabase() { 31 | abstract fun songDao(): SongDao 32 | abstract fun chartDao(): ChartDao 33 | abstract fun songWithChartDao(): SongWithChartsDao 34 | abstract fun aliasDao(): AliasDao 35 | abstract fun recordDao(): RecordDao 36 | abstract fun chartStatsDao(): ChartStatsDao 37 | 38 | 39 | companion object { 40 | const val DATABASE_VERSION = 1 41 | const val DATABASE_NAME = "maimaidata_db" 42 | 43 | @Volatile 44 | private lateinit var instance: AppDataBase 45 | 46 | 47 | fun getInstance(): AppDataBase { 48 | if (!::instance.isInitialized) { 49 | throw IllegalStateException("AppDataBase must be initialized first. Call init(context) before getInstance().") 50 | } 51 | return instance 52 | } 53 | 54 | fun init(context: Context): AppDataBase { 55 | instance = Room.databaseBuilder( 56 | context.applicationContext, 57 | AppDataBase::class.java, 58 | DATABASE_NAME 59 | ) 60 | .fallbackToDestructiveMigration(BuildConfig.DEBUG) 61 | .addCallback(object : Callback() { 62 | override fun onDestructiveMigration(connection: SQLiteConnection) { 63 | SpUtil.setDataVersion("0") 64 | } 65 | }) 66 | .build() 67 | return instance 68 | } 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/dao/AliasDao.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.dao 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.Query 7 | import com.paperpig.maimaidata.db.entity.AliasEntity 8 | 9 | @Dao 10 | interface AliasDao { 11 | 12 | /** 13 | * 根据歌曲 ID 查询对应的别名列表。 14 | * 15 | * @param songId 歌曲的唯一标识 ID。 16 | * @return 包含该歌曲所有别名的 LiveData 列表,用于响应式更新 UI。 17 | */ 18 | @Query("SELECT * FROM alias WHERE song_id = :songId") 19 | fun getAliasListBySongId(songId: Int): LiveData> 20 | 21 | 22 | @Insert 23 | fun insertAllAlias(aliasList: List) 24 | 25 | 26 | @Query("DELETE FROM alias") 27 | fun clearAlias() 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/dao/ChartDao.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.dao 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.Query 7 | import com.paperpig.maimaidata.db.entity.ChartEntity 8 | import com.paperpig.maimaidata.model.MaxNotesStats 9 | 10 | @Dao 11 | interface ChartDao { 12 | 13 | /** 14 | * 查询 chart 表中各类音符的最大值(排除 UTAGE 和 UTAGE_PLAYER2)。 15 | * 返回一个包含最大值的 MaxNotesStats。 16 | */ 17 | @Query( 18 | """ 19 | SELECT 20 | MAX(notes_tap) AS tap, 21 | MAX(notes_hold) AS hold, 22 | MAX(notes_slide) AS slide, 23 | MAX(notes_touch) AS touch, 24 | MAX(notes_break) AS break_, 25 | MAX(notes_total) AS total 26 | FROM chart 27 | WHERE difficulty_type NOT IN ('UTAGE', 'UTAGE_PLAYER2') 28 | """ 29 | ) 30 | fun getMaxNotes(): LiveData 31 | 32 | 33 | @Insert 34 | fun insertAllCharts(chartList: List) 35 | 36 | 37 | @Query("DELETE FROM chart") 38 | fun clearCharts() 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/dao/ChartStatsDao.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.dao 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.LiveData 5 | import androidx.room.Dao 6 | import androidx.room.Insert 7 | import androidx.room.Query 8 | import androidx.room.Transaction 9 | import com.paperpig.maimaidata.db.AppDataBase 10 | import com.paperpig.maimaidata.db.entity.ChartStatsEntity 11 | 12 | @Dao 13 | interface ChartStatsDao { 14 | 15 | /** 16 | * 批量替换所有水鱼谱面统计数据。 17 | * @param chartStatsList 水鱼谱面统计列表 18 | * @return 操作结果 19 | */ 20 | @Transaction 21 | fun replaceAllChartStats(chartStatsList: List): Boolean { 22 | try { 23 | clearChartStats() 24 | insertAllChartStats(chartStatsList) 25 | return true 26 | } catch (e: Exception) { 27 | Log.e( 28 | AppDataBase.DATABASE_NAME, "Transaction replaceAllChartStats failed: ${e.message}" 29 | ) 30 | return false 31 | } 32 | } 33 | 34 | /** 35 | * 根据谱面 ID 和难度索引查询水鱼谱面统计数据 36 | * @param songId 谱面 ID 37 | * @param index 普通谱面 0 = basic,1 = advanced,2 = expert,3 = master,4 = remaster,宴会场 0 = 单人谱面或者1p谱面 , 1 = 2p谱面 38 | * @return 水鱼谱面统计数据 39 | */ 40 | @Query("SELECT * FROM chart_stats WHERE song_id = :songId AND level_index = :index") 41 | fun getChartStatsBySongIdAndDifficultyIndex(songId: Int, index: Int): LiveData 42 | 43 | 44 | @Insert 45 | fun insertAllChartStats(list: List) 46 | 47 | @Query("DELETE FROM chart_stats") 48 | fun clearChartStats() 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/dao/RecordDao.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.dao 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.LiveData 5 | import androidx.room.Dao 6 | import androidx.room.Insert 7 | import androidx.room.Query 8 | import androidx.room.Transaction 9 | import com.paperpig.maimaidata.db.AppDataBase 10 | import com.paperpig.maimaidata.db.entity.RecordEntity 11 | 12 | @Dao 13 | interface RecordDao { 14 | 15 | /** 16 | * 批量替换所有记录数据。 17 | * @param recordList 记录列表 18 | * @return 操作结果 19 | */ 20 | @Transaction 21 | fun replaceAllRecord(recordList: List): Boolean { 22 | try { 23 | clearRecord() 24 | insertAllRecord(recordList) 25 | return true 26 | } catch (e: Exception) { 27 | Log.e(AppDataBase.DATABASE_NAME, "Transaction replaceAllRecord failed: ${e.message}") 28 | return false 29 | } 30 | } 31 | 32 | @Query("SELECT * FROM record") 33 | fun getAllRecords(): LiveData> 34 | 35 | /** 36 | * 根据难度索引查询记录表 37 | * @param index 难度索引 38 | * @return 记录列表 39 | */ 40 | @Query("SELECT * FROM record WHERE level_index = :index") 41 | fun getRecordsByDifficultyIndex(index: Int): LiveData> 42 | 43 | 44 | /** 45 | * 根据歌曲ID获取记录表 46 | * @param songId 歌曲ID 47 | * @return 记录列表 48 | */ 49 | @Query("SELECT * FROM record WHERE song_id = :songId") 50 | fun getRecordsBySongId(songId: Int): LiveData> 51 | 52 | @Insert 53 | fun insertAllRecord(list: List) 54 | 55 | @Query("DELETE FROM record") 56 | fun clearRecord() 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/dao/SongDao.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import com.paperpig.maimaidata.db.entity.SongDataEntity 7 | 8 | @Dao 9 | interface SongDao { 10 | 11 | @Insert 12 | fun insertAllSongs(songDataList: List) 13 | 14 | @Query("DELETE FROM song_data") 15 | fun clearSongData() 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/entity/AliasEntity.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | 8 | 9 | @Entity( 10 | tableName = "alias", 11 | foreignKeys = [ForeignKey( 12 | entity = SongDataEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["song_id"], 15 | onDelete = ForeignKey.CASCADE 16 | )] 17 | ) 18 | data class AliasEntity( 19 | // 主键(自增长,默认值 0) 20 | @PrimaryKey(autoGenerate = true) 21 | val id: Int = 0, 22 | 23 | // 外键关联 SongDataEntity.id 24 | @ColumnInfo(name = "song_id", index = true) // 添加索引提升查询性能 25 | val songId: Int, 26 | 27 | // 别名信息 28 | val alias: String 29 | ) 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/entity/ChartEntity.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.ForeignKey 7 | import androidx.room.PrimaryKey 8 | import com.paperpig.maimaidata.model.DifficultyType 9 | import kotlinx.parcelize.Parcelize 10 | 11 | @Parcelize 12 | @Entity( 13 | tableName = "chart", 14 | foreignKeys = [ForeignKey( 15 | entity = SongDataEntity::class, 16 | parentColumns = ["id"], 17 | childColumns = ["song_id"], 18 | onDelete = ForeignKey.CASCADE 19 | )] 20 | ) 21 | data class ChartEntity( 22 | // 主键(自增长,默认值 0) 23 | @PrimaryKey(autoGenerate = true) 24 | val id: Long = 0, 25 | 26 | // 外键关联 SongDataEntity.id 27 | @ColumnInfo(name = "song_id", index = true) // 添加索引提升查询性能 28 | val songId: Int, 29 | 30 | // 难度类型(如BASIC/ADVANCED/EXPERT/MASTER/REMASTER) 31 | @ColumnInfo(name = "difficulty_type") 32 | val difficultyType: DifficultyType, 33 | 34 | // 标准 or DX 35 | val type: String, 36 | 37 | // 当前定数 38 | val ds: Double, 39 | 40 | // 旧版本定数 41 | @ColumnInfo(name = "old_ds") 42 | val oldDs: Double?, 43 | 44 | // 难度等级(如 "12") 45 | val level: String, 46 | 47 | // 谱面作者 48 | val charter: String, 49 | 50 | // 音符统计(显式指定列名) 51 | @ColumnInfo(name = "notes_tap") 52 | val notesTap: Int, 53 | 54 | @ColumnInfo(name = "notes_hold") 55 | val notesHold: Int, 56 | 57 | @ColumnInfo(name = "notes_slide") 58 | val notesSlide: Int, 59 | 60 | @ColumnInfo(name = "notes_touch") 61 | val notesTouch: Int, 62 | 63 | @ColumnInfo(name = "notes_break") 64 | val notesBreak: Int, 65 | 66 | @ColumnInfo(name = "notes_total") 67 | val notesTotal: Int 68 | ) : Parcelable 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/entity/ChartStatsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | import androidx.room.TypeConverter 8 | import androidx.room.TypeConverters 9 | import com.google.gson.Gson 10 | import com.google.gson.reflect.TypeToken 11 | 12 | 13 | @Entity( 14 | tableName = "chart_stats", 15 | foreignKeys = [ForeignKey( 16 | entity = SongDataEntity::class, 17 | parentColumns = ["id"], 18 | childColumns = ["song_id"] 19 | )] 20 | ) 21 | data class ChartStatsEntity( 22 | // 主键(自增长,默认值 0) 23 | @PrimaryKey(autoGenerate = true) 24 | val id: Long = 0, 25 | 26 | // JSON 的 key 27 | @ColumnInfo(name = "song_id", index = true) 28 | val songId: Int, 29 | 30 | // 样本数量 31 | val cnt: Double?, 32 | 33 | // 谱面的官标难度等级 34 | val diff: String?, 35 | 36 | // 等级索引 37 | @ColumnInfo(name = "level_index") 38 | val levelIndex: Int, 39 | 40 | // 谱面的拟合难度 41 | @ColumnInfo(name = "fit_diff") 42 | val fitDiff: Double?, 43 | 44 | // 谱面平均达成率 45 | val avg: Double?, 46 | 47 | // 谱面平均 DX Scores 48 | @ColumnInfo(name = "avg_dx") 49 | val avgDx: Double?, 50 | 51 | // 谱面达成率的标准差 52 | @ColumnInfo(name = "std_dev") 53 | val stdDev: Double?, 54 | 55 | // 评级分布 56 | @TypeConverters(ListIntConverter::class) 57 | val dist: List?, 58 | 59 | // Full Combo 分布 60 | @ColumnInfo(name = "fc_dist") 61 | @TypeConverters(ListIntConverter::class) 62 | val fcDist: List? 63 | ) 64 | 65 | class ListIntConverter { 66 | @TypeConverter 67 | fun fromList(value: List?): String? { 68 | return Gson().toJson(value) 69 | } 70 | 71 | @TypeConverter 72 | fun toList(value: String?): List? { 73 | return Gson().fromJson(value, object : TypeToken>() {}.type) 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/entity/SongDataEntity.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.annotation.ColorRes 5 | import androidx.room.ColumnInfo 6 | import androidx.room.Entity 7 | import androidx.room.Ignore 8 | import androidx.room.PrimaryKey 9 | import com.paperpig.maimaidata.R 10 | import kotlinx.parcelize.IgnoredOnParcel 11 | import kotlinx.parcelize.Parcelize 12 | 13 | @Parcelize 14 | @Entity(tableName = "song_data") 15 | data class SongDataEntity( 16 | // 主键(歌曲id) 17 | @PrimaryKey 18 | val id: Int, 19 | 20 | // 标题 21 | val title: String, 22 | 23 | // 标题假名(有汉字时) 24 | @ColumnInfo(name = "title_kana") 25 | val titleKana: String, 26 | 27 | // 作曲家 28 | val artist: String, 29 | 30 | // 图片地址 31 | @ColumnInfo(name = "image_url") 32 | val imageUrl: String, 33 | 34 | // 歌曲流派 35 | val genre: String, 36 | 37 | // 歌曲流派(日) 38 | @ColumnInfo(name = "cat_code") 39 | val catCode: String, 40 | 41 | // bpm 42 | val bpm: Int, 43 | 44 | // 添加版本 45 | val from: String, 46 | 47 | // 标准 or DX 48 | val type: String, 49 | 50 | // 添加版本(日) 51 | val version: String, 52 | 53 | // 是否为新版本歌曲 54 | @ColumnInfo(name = "is_new") 55 | val isNew: Boolean, 56 | 57 | // 宴会场分类汉字 58 | val kanji: String?, 59 | 60 | // 宴会场说明 61 | val comment: String?, 62 | 63 | // 双人协谱标记 64 | val buddy: String?, 65 | ) : Parcelable { 66 | @IgnoredOnParcel 67 | @Ignore 68 | @ColorRes 69 | val bgColor: Int = when (genre) { 70 | "流行&动漫" -> R.color.pop 71 | "niconico & VOCALOID" -> R.color.vocal 72 | "东方Project" -> R.color.touhou 73 | "其他游戏" -> R.color.variety 74 | "舞萌" -> R.color.maimai 75 | "宴会場" -> R.color.utage 76 | else -> R.color.gekichuni 77 | } 78 | 79 | @IgnoredOnParcel 80 | @Ignore 81 | @ColorRes 82 | val strokeColor: Int = 83 | when (genre) { 84 | "流行&动漫" -> R.color.pop_stroke 85 | "niconico & VOCALOID" -> R.color.vocal_stroke 86 | "东方Project" -> R.color.touhou_stroke 87 | "其他游戏" -> R.color.variety_stroke 88 | "舞萌" -> R.color.maimai_stroke 89 | "宴会場" -> R.color.utage_stroke 90 | else -> R.color.gekichuni_stroke 91 | } 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/db/entity/SongWithChartsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.db.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.room.Embedded 5 | import androidx.room.Relation 6 | import kotlinx.parcelize.Parcelize 7 | 8 | 9 | @Parcelize 10 | data class SongWithChartsEntity( 11 | @Embedded val songData: SongDataEntity, 12 | @Relation( 13 | parentColumn = "id", 14 | entityColumn = "song_id" 15 | ) 16 | val charts: List 17 | ) : Parcelable 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/glide/MyGlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.glide 2 | 3 | import android.content.Context 4 | import com.bumptech.glide.Glide 5 | import com.bumptech.glide.Registry 6 | import com.bumptech.glide.annotation.GlideModule 7 | import com.bumptech.glide.load.model.GlideUrl 8 | import com.bumptech.glide.module.AppGlideModule 9 | import com.paperpig.maimaidata.network.OkHttpUrlLoader 10 | import com.paperpig.maimaidata.network.UnsafeOkHttpClient.unsafeOkHttpClient 11 | import java.io.InputStream 12 | 13 | @GlideModule 14 | class MyGlideModule : AppGlideModule() { 15 | 16 | override fun registerComponents(context: Context, glide: Glide, registry: Registry) { 17 | val okHttpClient = unsafeOkHttpClient.build() 18 | registry.replace( 19 | GlideUrl::class.java, 20 | InputStream::class.java, 21 | OkHttpUrlLoader.Factory(okHttpClient) 22 | ) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/AppUpdateModel.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * @author BBS 7 | * @since 2021/9/6 8 | */ 9 | data class AppUpdateModel( 10 | /** 11 | * apk version string 12 | */ 13 | @SerializedName("apk_version") 14 | var version: String? = null, 15 | 16 | /** 17 | * newest apk url 18 | */ 19 | @SerializedName("apk_url") 20 | var url: String? = null, 21 | 22 | /** 23 | * update info 24 | */ 25 | @SerializedName("apk_info") 26 | var info: String? = null, 27 | 28 | /** 29 | * json data version string 30 | */ 31 | @SerializedName("data_version_2") 32 | var dataVersion2: String? = null, 33 | 34 | /** 35 | * new json url 36 | */ 37 | @SerializedName("data_url_2") 38 | var dataUrl2: String? = null 39 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/ChartsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class ChartsResponse( 7 | @SerializedName("charts") 8 | val charts: Map> 9 | ) 10 | 11 | data class ChartData( 12 | @SerializedName("cnt") 13 | val cnt: Double?, 14 | @SerializedName("diff") 15 | val diff: String?, 16 | @SerializedName("fit_diff") 17 | val fitDiff: Double?, 18 | @SerializedName("avg") 19 | val avg: Double?, 20 | @SerializedName("avg_dx") 21 | val avgDx: Double?, 22 | @SerializedName("std_dev") 23 | val stdDev: Double?, 24 | @SerializedName("dist") 25 | val dist: List?, 26 | @SerializedName("fc_dist") 27 | val fcDist: List? 28 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/DifficultyType.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | enum class DifficultyType { 4 | BASIC, // 初级 5 | ADVANCED, // 高级 6 | EXPERT, // 专家 7 | MASTER, // 大师 8 | REMASTER, // 宗师 9 | UTAGE, // 宴会场 10 | UTAGE_PLAYER2, // 宴会场2P 11 | UNKNOWN // 未知(可选,用于错误处理) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/DsSongData.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | /** 4 | * 用于定数排序的歌曲 5 | */ 6 | data class DsSongData( 7 | /** 8 | * 歌曲id 9 | */ 10 | val songId: Int, 11 | 12 | /** 13 | * 歌曲标题 14 | */ 15 | val title: String, 16 | 17 | /** 18 | * 谱面类型 19 | */ 20 | val type: String, 21 | 22 | /** 23 | * 曲封url 24 | */ 25 | val imageUrl: String?, 26 | 27 | /** 28 | * 难度索引 29 | */ 30 | val levelIndex: Int, 31 | 32 | /** 33 | * 定数 34 | */ 35 | val ds: Double 36 | ) 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/MaxNotesStats.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | data class MaxNotesStats( 4 | val tap: Int, 5 | val hold: Int, 6 | val slide: Int, 7 | val touch: Int, 8 | val break_: Int, // break 是关键字 9 | val total: Int 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/Rating.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | data class Rating( 4 | /** 5 | * 定数 6 | */ 7 | val innerLevel: Float, 8 | 9 | /** 10 | * 达成率 11 | */ 12 | val achi: String, 13 | 14 | /** 15 | * 单曲rating 16 | */ 17 | val rating: Int, 18 | 19 | /** 20 | * 合计rating 21 | */ 22 | val total: Int 23 | ) 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/ResponseErrorBody.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | data class ResponseErrorBody(val message: String) 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/SongData.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | import android.os.Parcelable 4 | import androidx.annotation.ColorRes 5 | import com.paperpig.maimaidata.R 6 | import kotlinx.parcelize.Parcelize 7 | import java.io.Serializable 8 | 9 | 10 | @Parcelize 11 | data class SongData( 12 | val basic_info: BasicInfo, 13 | val charts: List, 14 | val ds: List, 15 | var old_ds: List, 16 | val id: String, 17 | val level: List, 18 | val title: String, 19 | val type: String, 20 | var alias: List?, 21 | ) : Parcelable { 22 | @ColorRes 23 | fun getBgColor() = 24 | when (basic_info.genre) { 25 | "流行&动漫" -> R.color.pop 26 | "niconico & VOCALOID" -> R.color.vocal 27 | "东方Project" -> R.color.touhou 28 | "其他游戏" -> R.color.variety 29 | "舞萌" -> R.color.maimai 30 | "宴会場" -> R.color.utage 31 | else -> R.color.gekichuni 32 | } 33 | 34 | fun getStrokeColor() = 35 | when (basic_info.genre) { 36 | "流行&动漫" -> R.color.pop_stroke 37 | "niconico & VOCALOID" -> R.color.vocal_stroke 38 | "东方Project" -> R.color.touhou_stroke 39 | "其他游戏" -> R.color.variety_stroke 40 | "舞萌" -> R.color.maimai_stroke 41 | "宴会場" -> R.color.utage_stroke 42 | else -> R.color.gekichuni_stroke 43 | } 44 | 45 | 46 | inner class BasicInfo( 47 | val artist: String, 48 | val bpm: Int, 49 | var from: String, 50 | var genre: String, 51 | val is_new: Boolean, 52 | val title: String, 53 | var image_url: String, 54 | var version: String, 55 | var kanji: String?, 56 | var comment: String?, 57 | var buddy: String? 58 | ) : Serializable 59 | 60 | inner class Chart( 61 | val charter: String, 62 | val notes: List 63 | ) : Serializable 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/model/Version.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.model 2 | 3 | import androidx.annotation.DrawableRes 4 | 5 | data class Version( 6 | /** 7 | * 版本名称 8 | */ 9 | val versionName: String, 10 | 11 | /** 12 | * 用于显示版本图片的drawable资源 13 | */ 14 | @DrawableRes val res: Int 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/MaimaiDataRequests.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonElement 5 | import com.paperpig.maimaidata.model.AppUpdateModel 6 | import com.paperpig.maimaidata.model.ChartsResponse 7 | import io.reactivex.Observable 8 | import io.reactivex.android.schedulers.AndroidSchedulers 9 | import io.reactivex.schedulers.Schedulers 10 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 11 | import okhttp3.RequestBody.Companion.toRequestBody 12 | import okhttp3.ResponseBody 13 | import retrofit2.Response 14 | 15 | 16 | /** 17 | * @author BBS 18 | * @since 2021/5/13 19 | */ 20 | object MaimaiDataRequests { 21 | /** 22 | * [MaimaiDataService.login] 23 | */ 24 | fun login(userName: String, password: String): Observable> { 25 | val requestBody = "{\"username\": \"$userName\", \"password\": \"$password\"}" 26 | .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) 27 | return MaimaiDataClient 28 | .instance 29 | .getService() 30 | .login(requestBody) 31 | .subscribeOn(Schedulers.io()) 32 | .observeOn(AndroidSchedulers.mainThread()) 33 | } 34 | 35 | /** 36 | * [MaimaiDataService.getRecords] 37 | */ 38 | fun getRecords(cookie: String): Observable = 39 | MaimaiDataClient 40 | .instance 41 | .getService() 42 | .getRecords(cookie) 43 | .compose(MaimaiDataTransformer.handleResult()) 44 | 45 | /** 46 | * fetch the version info for updating 47 | */ 48 | fun fetchUpdateInfo(): Observable = 49 | MaimaiDataClient 50 | .instance 51 | .getService() 52 | .getUpdateInfo() 53 | .compose(MaimaiDataTransformer.handleResult()) 54 | .flatMap { 55 | val model = Gson().fromJson(it, AppUpdateModel::class.java) 56 | Observable.just(model) 57 | } 58 | 59 | /** 60 | * get chart_status json 61 | */ 62 | fun getChartStatus(): Observable = 63 | MaimaiDataClient 64 | .instance 65 | .getService() 66 | .getChartStatus() 67 | .compose(MaimaiDataTransformer.handleResult()) 68 | .flatMap { 69 | val model = Gson().fromJson(it, ChartsResponse::class.java) 70 | Observable.just(model) 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/MaimaiDataService.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network 2 | 3 | import com.google.gson.JsonElement 4 | import io.reactivex.Observable 5 | import okhttp3.RequestBody 6 | import okhttp3.ResponseBody 7 | import retrofit2.Response 8 | import retrofit2.http.Body 9 | import retrofit2.http.GET 10 | import retrofit2.http.Header 11 | import retrofit2.http.Headers 12 | import retrofit2.http.POST 13 | 14 | /** 15 | * @author BBS 16 | * @since 2021-05-13 17 | */ 18 | interface MaimaiDataService { 19 | 20 | /** 21 | * get login info 22 | * set cookie: jwt_token 23 | * use jwt_token to get player's record 24 | * 25 | * @param body param json like {"username": string, "password": string} 26 | */ 27 | @Headers("Content-Type:application/json;charset=UTF-8") 28 | @POST("/api/maimaidxprober/login") 29 | fun login(@Body body: RequestBody): Observable> 30 | 31 | /** 32 | * player's record of songs 33 | * record on diving-fish.com manually 34 | */ 35 | @GET("/api/maimaidxprober/player/records") 36 | fun getRecords(@Header("Cookie") cookie: String): Observable 37 | 38 | /** 39 | * fetch update info from a noob's server 40 | */ 41 | @Headers("urlName:https://bucket-1256206908.cos.ap-shanghai.myqcloud.com") 42 | @GET("/update.json") 43 | fun getUpdateInfo(): Observable 44 | 45 | /** 46 | * get chart_status from diving-fish.com 47 | */ 48 | @GET("/api/maimaidxprober/chart_stats") 49 | fun getChartStatus():Observable 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/MaimaiDataTransformer.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network 2 | 3 | import com.google.gson.JsonElement 4 | import io.reactivex.ObservableTransformer 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | 8 | 9 | /** 10 | * @author BBS 11 | * @since 2021-05-13 12 | */ 13 | class MaimaiDataTransformer { 14 | companion object { 15 | /** 16 | * switch thread & check err code 17 | */ 18 | fun handleResult(): ObservableTransformer { 19 | return ObservableTransformer { upstream -> 20 | upstream 21 | .subscribeOn(Schedulers.io()) 22 | .observeOn(AndroidSchedulers.mainThread()) 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/OkHttpUrlLoader.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network 2 | 3 | import com.bumptech.glide.load.Options 4 | import com.bumptech.glide.load.model.GlideUrl 5 | import com.bumptech.glide.load.model.ModelLoader 6 | import com.bumptech.glide.load.model.ModelLoader.LoadData 7 | import com.bumptech.glide.load.model.ModelLoaderFactory 8 | import com.bumptech.glide.load.model.MultiModelLoaderFactory 9 | import okhttp3.Call 10 | import okhttp3.OkHttpClient 11 | import java.io.InputStream 12 | 13 | class OkHttpUrlLoader // Public API. 14 | (private val client: Call.Factory) : ModelLoader { 15 | override fun handles(url: GlideUrl): Boolean { 16 | return true 17 | } 18 | 19 | override fun buildLoadData( 20 | model: GlideUrl, width: Int, height: Int, options: Options 21 | ): LoadData { 22 | return LoadData(model, OkHttpStreamFetcher(client, model)) 23 | } 24 | 25 | /** The default factory for [OkHttpUrlLoader]s. */ // Public API. 26 | class Factory 27 | /** Constructor for a new Factory that runs requests using a static singleton client. */ @JvmOverloads constructor( 28 | private val client: Call.Factory = internalClient!! 29 | ) : ModelLoaderFactory { 30 | override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { 31 | return OkHttpUrlLoader(client) 32 | } 33 | 34 | override fun teardown() { 35 | // Do nothing, this instance doesn't own the client. 36 | } 37 | 38 | companion object { 39 | @Volatile 40 | private var internalClient: Call.Factory? = null 41 | get() { 42 | if (field == null) { 43 | synchronized(Factory::class.java) { 44 | if (field == null) { 45 | field = OkHttpClient() 46 | } 47 | } 48 | } 49 | return field 50 | } 51 | } 52 | /** 53 | * Constructor for a new Factory that runs requests using given client. 54 | * 55 | * @param client this is typically an instance of `OkHttpClient`. 56 | */ 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/UnsafeOkHttpClient.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network 2 | 3 | import okhttp3.OkHttpClient 4 | import java.security.SecureRandom 5 | import java.security.cert.X509Certificate 6 | import javax.net.ssl.SSLContext 7 | import javax.net.ssl.TrustManager 8 | import javax.net.ssl.X509TrustManager 9 | 10 | object UnsafeOkHttpClient { 11 | // Create a trust manager that does not validate certificate chains 12 | @JvmStatic 13 | val unsafeOkHttpClient: OkHttpClient.Builder 14 | 15 | // Install the all-trusting trust manager 16 | 17 | // Create an ssl socket factory with our all-trusting manager 18 | get() = try { 19 | // Create a trust manager that does not validate certificate chains 20 | val trustAllCerts = 21 | arrayOf( 22 | object : X509TrustManager { 23 | override fun checkClientTrusted( 24 | chain: Array, 25 | authType: String 26 | ) = Unit 27 | 28 | override fun checkServerTrusted( 29 | chain: Array, 30 | authType: String 31 | ) = Unit 32 | 33 | override fun getAcceptedIssuers(): Array { 34 | return arrayOf() 35 | } 36 | } 37 | ) 38 | 39 | // Install the all-trusting trust manager 40 | val sslContext = 41 | SSLContext.getInstance("SSL") 42 | sslContext.init(null, trustAllCerts, SecureRandom()) 43 | 44 | // Create an ssl socket factory with our all-trusting manager 45 | val sslSocketFactory = sslContext.socketFactory 46 | val builder = OkHttpClient.Builder() 47 | builder.sslSocketFactory( 48 | sslSocketFactory, 49 | trustAllCerts[0] as X509TrustManager 50 | ) 51 | builder.hostnameVerifier { _, _ -> true } 52 | } catch (e: Exception) { 53 | throw RuntimeException(e) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/server/HttpRedirectServer.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.server; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.IOException; 6 | 7 | import fi.iki.elonen.NanoHTTPD; 8 | 9 | public class HttpRedirectServer extends NanoHTTPD { 10 | public static int Port = 9457; 11 | private final static String TAG = "HttpRedirectServer"; 12 | 13 | protected HttpRedirectServer() throws IOException { 14 | super(Port); 15 | } 16 | 17 | @Override 18 | public void start() throws IOException { 19 | super.start(); 20 | Log.d(TAG, "Http server running on http://localhost:" + Port); 21 | } 22 | 23 | @Override 24 | public Response serve(IHTTPSession session) { 25 | return newFixedLengthResponse( 26 | Response.Status.ACCEPTED, 27 | MIME_HTML, 28 | "

登录信息已获取,可关闭该窗口并请切回到更新器等待分数上传!

"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/server/HttpServer.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.server; 2 | 3 | import android.util.Log; 4 | 5 | import com.paperpig.maimaidata.crawler.CrawlerCaller; 6 | 7 | import java.io.IOException; 8 | 9 | import fi.iki.elonen.NanoHTTPD; 10 | 11 | 12 | public class HttpServer extends NanoHTTPD { 13 | public static int Port = 8284; 14 | private final static String TAG = "HttpServer"; 15 | 16 | protected HttpServer() throws IOException { 17 | super(Port); 18 | } 19 | 20 | @Override 21 | public void start() throws IOException { 22 | super.start(); 23 | Log.d(TAG, "Http server running on http://localhost:" + Port); 24 | } 25 | 26 | @Override 27 | public Response serve(IHTTPSession session) { 28 | Log.d(TAG, "Serve request: " + session.getUri()); 29 | switch (session.getUri()) { 30 | case "/auth": 31 | return redirectToWechatAuthUrl(session); 32 | default: 33 | return redirectToAuthUrlWithRandomParm(session); 34 | } 35 | } 36 | 37 | // To avoid fu***ing cache of wechat webview client 38 | private Response redirectToAuthUrlWithRandomParm(IHTTPSession session) { 39 | Response r = newFixedLengthResponse(Response.Status.REDIRECT, MIME_HTML, ""); 40 | r.addHeader("Location", "http://127.0.0.1:8284/auth?random=" + System.currentTimeMillis()); 41 | return r; 42 | } 43 | 44 | private Response redirectToWechatAuthUrl(IHTTPSession session) { 45 | String url = CrawlerCaller.INSTANCE.getWechatAuthUrl(); 46 | if (url == null) 47 | return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_HTML, ""); 48 | Log.d(TAG, url); 49 | 50 | Response r = newFixedLengthResponse(Response.Status.REDIRECT, MIME_HTML, ""); 51 | r.addHeader("Location", url); 52 | r.addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 53 | r.addHeader("Pragma", "no-cache"); 54 | r.addHeader("Expires", "0"); 55 | return r; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/server/HttpServerService.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.server; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import java.io.IOException; 11 | 12 | public class HttpServerService extends Service { 13 | private static final String TAG = "HttpServerService"; 14 | private HttpServer httpServer; 15 | private HttpRedirectServer httpRedirectServer; 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | Log.d("HttpService", "Http service on create"); 21 | try { 22 | if (this.httpServer != null) this.httpServer.stop(); 23 | if (this.httpRedirectServer != null) this.httpRedirectServer.stop(); 24 | this.httpServer = new HttpServer(); 25 | this.httpRedirectServer = new HttpRedirectServer(); 26 | } catch (IOException e) { 27 | Log.d(TAG, "Error while create HttpServerService: " + e); 28 | } 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public IBinder onBind(Intent intent) { 34 | return null; 35 | } 36 | 37 | @Override 38 | public int onStartCommand(Intent intent, int flags, int startId) { 39 | super.onStartCommand(intent, flags, startId); 40 | Log.d("HttpService", "Http service on start command"); 41 | try { 42 | this.httpServer.start(); 43 | this.httpRedirectServer.start(); 44 | } catch (IOException e) { 45 | Log.d(TAG, "Error while start HttpServerService: " + e); 46 | } 47 | return START_STICKY; 48 | } 49 | 50 | @Override 51 | public void onDestroy() { 52 | super.onDestroy(); 53 | this.httpServer.stop(); 54 | this.httpRedirectServer.stop(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/core/Constant.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.core; 2 | 3 | public class Constant { 4 | public static final String TAG = "VpnProxy"; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/core/NatSession.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.core; 2 | 3 | public class NatSession { 4 | public int RemoteIP; 5 | public short RemotePort; 6 | public String RemoteHost; 7 | public int BytesSent; 8 | public int PacketSent; 9 | public long LastNanoTime; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/core/NatSessionManager.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.core; 2 | 3 | import android.util.SparseArray; 4 | 5 | import com.paperpig.maimaidata.network.vpn.tcpip.CommonMethods; 6 | 7 | 8 | public class NatSessionManager { 9 | 10 | static final int MAX_SESSION_COUNT = 4096; 11 | static final long SESSION_TIMEOUT_NS = 120 * 1000000000L; 12 | static final SparseArray Sessions = new SparseArray(); 13 | 14 | public static NatSession getSession(int portKey) { 15 | return Sessions.get(portKey); 16 | } 17 | 18 | public static int getSessionCount() { 19 | return Sessions.size(); 20 | } 21 | 22 | static void clearExpiredSessions() { 23 | long now = System.nanoTime(); 24 | for (int i = Sessions.size() - 1; i >= 0; i--) { 25 | NatSession session = Sessions.valueAt(i); 26 | if (now - session.LastNanoTime > SESSION_TIMEOUT_NS) { 27 | Sessions.removeAt(i); 28 | } 29 | } 30 | } 31 | 32 | public static void clearAllSessions() { 33 | Sessions.clear(); 34 | } 35 | 36 | public static NatSession createSession(int portKey, int remoteIP, short remotePort) { 37 | if (Sessions.size() > MAX_SESSION_COUNT) { 38 | clearExpiredSessions(); 39 | } 40 | 41 | NatSession session = new NatSession(); 42 | session.LastNanoTime = System.nanoTime(); 43 | session.RemoteIP = remoteIP; 44 | session.RemotePort = remotePort; 45 | 46 | // if (ProxyConfig.isFakeIP(remoteIP)) { 47 | // session.RemoteHost = DnsProxy.reverseLookup(remoteIP); 48 | // } 49 | 50 | if (session.RemoteHost == null) { 51 | session.RemoteHost = CommonMethods.ipIntToString(remoteIP); 52 | } 53 | 54 | Sessions.put(portKey, session); 55 | return session; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/core/TunnelFactory.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.core; 2 | 3 | import android.util.Log; 4 | 5 | import com.paperpig.maimaidata.network.server.HttpRedirectServer; 6 | import com.paperpig.maimaidata.network.vpn.tunnel.HttpCapturerTunnel; 7 | import com.paperpig.maimaidata.network.vpn.tunnel.RawTunnel; 8 | import com.paperpig.maimaidata.network.vpn.tunnel.Tunnel; 9 | 10 | import java.net.InetSocketAddress; 11 | import java.nio.channels.Selector; 12 | import java.nio.channels.SocketChannel; 13 | 14 | public class TunnelFactory { 15 | private final static String TAG = "TunnelFactory"; 16 | 17 | public static Tunnel wrap(SocketChannel channel, Selector selector) throws Exception { 18 | return new RawTunnel(channel, selector); 19 | } 20 | 21 | public static Tunnel createTunnelByConfig(InetSocketAddress destAddress, Selector selector) throws Exception { 22 | Log.d(TAG, destAddress.getHostName() + ":" + destAddress.getPort()); 23 | if (destAddress.getAddress() != null) 24 | { 25 | Log.d(TAG, destAddress.getAddress().toString()); 26 | } 27 | if (destAddress.getHostName().endsWith("wahlap.com") && destAddress.getPort() == 80) { 28 | Log.d(TAG, "Request for wahlap.com caught"); 29 | return new HttpCapturerTunnel( 30 | new InetSocketAddress("127.0.0.1", HttpRedirectServer.Port), selector); 31 | } else { 32 | if (destAddress.isUnresolved()) 33 | return new RawTunnel(new InetSocketAddress(destAddress.getHostName(), destAddress.getPort()), selector); 34 | else 35 | return new RawTunnel(destAddress, selector); 36 | } 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/dns/DnsFlags.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.dns; 2 | 3 | public class DnsFlags { 4 | public boolean QR;//1 bits 5 | public int OpCode;//4 bits 6 | public boolean AA;//1 bits 7 | public boolean TC;//1 bits 8 | public boolean RD;//1 bits 9 | public boolean RA;//1 bits 10 | public int Zero;//3 bits 11 | public int Rcode;//4 bits 12 | 13 | public static DnsFlags Parse(short value) { 14 | int m_Flags = value & 0xFFFF; 15 | DnsFlags flags = new DnsFlags(); 16 | flags.QR = ((m_Flags >> 7) & 0x01) == 1; 17 | flags.OpCode = (m_Flags >> 3) & 0x0F; 18 | flags.AA = ((m_Flags >> 2) & 0x01) == 1; 19 | flags.TC = ((m_Flags >> 1) & 0x01) == 1; 20 | flags.RD = (m_Flags & 0x01) == 1; 21 | flags.RA = (m_Flags >> 15) == 1; 22 | flags.Zero = (m_Flags >> 12) & 0x07; 23 | flags.Rcode = ((m_Flags >> 8) & 0xF); 24 | return flags; 25 | } 26 | 27 | public short ToShort() { 28 | int m_Flags = 0; 29 | m_Flags |= (this.QR ? 1 : 0) << 7; 30 | m_Flags |= (this.OpCode & 0x0F) << 3; 31 | m_Flags |= (this.AA ? 1 : 0) << 2; 32 | m_Flags |= (this.TC ? 1 : 0) << 1; 33 | m_Flags |= this.RD ? 1 : 0; 34 | m_Flags |= (this.RA ? 1 : 0) << 15; 35 | m_Flags |= (this.Zero & 0x07) << 12; 36 | m_Flags |= (this.Rcode & 0x0F) << 8; 37 | return (short) m_Flags; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/dns/Question.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.dns; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public class Question { 6 | public String Domain; 7 | public short Type; 8 | public short Class; 9 | 10 | private int offset; 11 | private int length; 12 | 13 | public static Question FromBytes(ByteBuffer buffer) { 14 | Question q = new Question(); 15 | q.offset = buffer.arrayOffset() + buffer.position(); 16 | q.Domain = DnsPacket.ReadDomain(buffer, buffer.arrayOffset()); 17 | q.Type = buffer.getShort(); 18 | q.Class = buffer.getShort(); 19 | q.length = buffer.arrayOffset() + buffer.position() - q.offset; 20 | return q; 21 | } 22 | 23 | public int Offset() { 24 | return offset; 25 | } 26 | 27 | public int Length() { 28 | return length; 29 | } 30 | 31 | public void ToBytes(ByteBuffer buffer) { 32 | this.offset = buffer.position(); 33 | DnsPacket.WriteDomain(this.Domain, buffer); 34 | buffer.putShort(this.Type); 35 | buffer.putShort(this.Class); 36 | this.length = buffer.position() - this.offset; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/dns/Resource.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.dns; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public class Resource { 6 | public String Domain; 7 | public short Type; 8 | public short Class; 9 | public int TTL; 10 | public short DataLength; 11 | public byte[] Data; 12 | 13 | private int offset; 14 | private int length; 15 | 16 | public static Resource FromBytes(ByteBuffer buffer) { 17 | 18 | Resource r = new Resource(); 19 | r.offset = buffer.arrayOffset() + buffer.position(); 20 | r.Domain = DnsPacket.ReadDomain(buffer, buffer.arrayOffset()); 21 | r.Type = buffer.getShort(); 22 | r.Class = buffer.getShort(); 23 | r.TTL = buffer.getInt(); 24 | r.DataLength = buffer.getShort(); 25 | r.Data = new byte[r.DataLength & 0xFFFF]; 26 | buffer.get(r.Data); 27 | r.length = buffer.arrayOffset() + buffer.position() - r.offset; 28 | return r; 29 | } 30 | 31 | public int Offset() { 32 | return offset; 33 | } 34 | 35 | public int Length() { 36 | return length; 37 | } 38 | 39 | public void ToBytes(ByteBuffer buffer) { 40 | if (this.Data == null) { 41 | this.Data = new byte[0]; 42 | } 43 | this.DataLength = (short) this.Data.length; 44 | 45 | this.offset = buffer.position(); 46 | DnsPacket.WriteDomain(this.Domain, buffer); 47 | buffer.putShort(this.Type); 48 | buffer.putShort(this.Class); 49 | buffer.putInt(this.TTL); 50 | 51 | buffer.putShort(this.DataLength); 52 | buffer.put(this.Data); 53 | this.length = buffer.position() - this.offset; 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/dns/ResourcePointer.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.dns; 2 | 3 | 4 | import com.paperpig.maimaidata.network.vpn.tcpip.CommonMethods; 5 | 6 | public class ResourcePointer { 7 | static final short offset_Domain = 0; 8 | static final short offset_Type = 2; 9 | static final short offset_Class = 4; 10 | static final int offset_TTL = 6; 11 | static final short offset_DataLength = 10; 12 | static final int offset_IP = 12; 13 | 14 | byte[] Data; 15 | int Offset; 16 | 17 | public ResourcePointer(byte[] data, int offset) { 18 | this.Data = data; 19 | this.Offset = offset; 20 | } 21 | 22 | public void setDomain(short value) { 23 | CommonMethods.writeShort(Data, Offset + offset_Domain, value); 24 | } 25 | 26 | public short getType() { 27 | return CommonMethods.readShort(Data, Offset + offset_Type); 28 | } 29 | 30 | public void setType(short value) { 31 | CommonMethods.writeShort(Data, Offset + offset_Type, value); 32 | } 33 | 34 | public short getClass(short value) { 35 | return CommonMethods.readShort(Data, Offset + offset_Class); 36 | } 37 | 38 | public void setClass(short value) { 39 | CommonMethods.writeShort(Data, Offset + offset_Class, value); 40 | } 41 | 42 | public int getTTL() { 43 | return CommonMethods.readInt(Data, Offset + offset_TTL); 44 | } 45 | 46 | public void setTTL(int value) { 47 | CommonMethods.writeInt(Data, Offset + offset_TTL, value); 48 | } 49 | 50 | public short getDataLength() { 51 | return CommonMethods.readShort(Data, Offset + offset_DataLength); 52 | } 53 | 54 | public void setDataLength(short value) { 55 | CommonMethods.writeShort(Data, Offset + offset_DataLength, value); 56 | } 57 | 58 | public int getIP() { 59 | return CommonMethods.readInt(Data, Offset + offset_IP); 60 | } 61 | 62 | public void setIP(int value) { 63 | CommonMethods.writeInt(Data, Offset + offset_IP, value); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/tcpip/TCPHeader.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.tcpip; 2 | 3 | import java.util.Locale; 4 | 5 | public class TCPHeader { 6 | 7 | public static final int FIN = 1; 8 | public static final int SYN = 2; 9 | public static final int RST = 4; 10 | public static final int PSH = 8; 11 | public static final int ACK = 16; 12 | public static final int URG = 32; 13 | 14 | static final short offset_src_port = 0; 15 | static final short offset_dest_port = 2; 16 | static final int offset_seq = 4; 17 | static final int offset_ack = 8; 18 | static final byte offset_lenres = 12; 19 | static final byte offset_flag = 13; 20 | static final short offset_win = 14; 21 | static final short offset_crc = 16; 22 | static final short offset_urp = 18; 23 | 24 | public byte[] m_Data; 25 | public int m_Offset; 26 | 27 | public TCPHeader(byte[] data, int offset) { 28 | this.m_Data = data; 29 | this.m_Offset = offset; 30 | } 31 | 32 | public int getHeaderLength() { 33 | int lenres = m_Data[m_Offset + offset_lenres] & 0xFF; 34 | return (lenres >> 4) * 4; 35 | } 36 | 37 | public short getSourcePort() { 38 | return CommonMethods.readShort(m_Data, m_Offset + offset_src_port); 39 | } 40 | 41 | public void setSourcePort(short value) { 42 | CommonMethods.writeShort(m_Data, m_Offset + offset_src_port, value); 43 | } 44 | 45 | public short getDestinationPort() { 46 | return CommonMethods.readShort(m_Data, m_Offset + offset_dest_port); 47 | } 48 | 49 | public void setDestinationPort(short value) { 50 | CommonMethods.writeShort(m_Data, m_Offset + offset_dest_port, value); 51 | } 52 | 53 | public byte getFlags() { 54 | return m_Data[m_Offset + offset_flag]; 55 | } 56 | 57 | public short getCrc() { 58 | return CommonMethods.readShort(m_Data, m_Offset + offset_crc); 59 | } 60 | 61 | public void setCrc(short value) { 62 | CommonMethods.writeShort(m_Data, m_Offset + offset_crc, value); 63 | } 64 | 65 | public int getSeqID() { 66 | return CommonMethods.readInt(m_Data, m_Offset + offset_seq); 67 | } 68 | 69 | public int getAckID() { 70 | return CommonMethods.readInt(m_Data, m_Offset + offset_ack); 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | // TODO Auto-generated method stub 76 | return String.format(Locale.ENGLISH, "%s%s%s%s%s%s%d->%d %s:%s", 77 | (getFlags() & SYN) == SYN ? "SYN " : "", 78 | (getFlags() & ACK) == ACK ? "ACK " : "", 79 | (getFlags() & PSH) == PSH ? "PSH " : "", 80 | (getFlags() & RST) == RST ? "RST " : "", 81 | (getFlags() & FIN) == FIN ? "FIN " : "", 82 | (getFlags() & URG) == URG ? "URG " : "", 83 | getSourcePort() & 0xFFFF, 84 | getDestinationPort() & 0xFFFF, 85 | getSeqID(), 86 | getAckID()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/tcpip/UDPHeader.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.tcpip; 2 | 3 | import java.util.Locale; 4 | 5 | public class UDPHeader { 6 | static final short offset_src_port = 0; // Source port 7 | static final short offset_dest_port = 2; // Destination port 8 | static final short offset_tlen = 4; // Datagram length 9 | static final short offset_crc = 6; // Checksum 10 | 11 | public byte[] m_Data; 12 | public int m_Offset; 13 | 14 | public UDPHeader(byte[] data, int offset) { 15 | this.m_Data = data; 16 | this.m_Offset = offset; 17 | } 18 | 19 | public short getSourcePort() { 20 | return CommonMethods.readShort(m_Data, m_Offset + offset_src_port); 21 | } 22 | 23 | public void setSourcePort(short value) { 24 | CommonMethods.writeShort(m_Data, m_Offset + offset_src_port, value); 25 | } 26 | 27 | public short getDestinationPort() { 28 | return CommonMethods.readShort(m_Data, m_Offset + offset_dest_port); 29 | } 30 | 31 | public void setDestinationPort(short value) { 32 | CommonMethods.writeShort(m_Data, m_Offset + offset_dest_port, value); 33 | } 34 | 35 | public int getTotalLength() { 36 | return CommonMethods.readShort(m_Data, m_Offset + offset_tlen) & 0xFFFF; 37 | } 38 | 39 | public void setTotalLength(int value) { 40 | CommonMethods.writeShort(m_Data, m_Offset + offset_tlen, (short) value); 41 | } 42 | 43 | public short getCrc() { 44 | return CommonMethods.readShort(m_Data, m_Offset + offset_crc); 45 | } 46 | 47 | public void setCrc(short value) { 48 | CommonMethods.writeShort(m_Data, m_Offset + offset_crc, value); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | // TODO Auto-generated method stub 54 | return String.format(Locale.ENGLISH, "%d->%d", getSourcePort() & 0xFFFF, 55 | getDestinationPort() & 0xFFFF); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/tunnel/Config.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.tunnel; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | public abstract class Config { 6 | public InetSocketAddress ServerAddress; 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/tunnel/HttpCapturerTunnel.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.tunnel; 2 | 3 | import android.util.Log; 4 | 5 | import com.paperpig.maimaidata.crawler.CrawlerCaller; 6 | 7 | import java.net.InetSocketAddress; 8 | import java.nio.ByteBuffer; 9 | import java.nio.channels.Selector; 10 | import java.nio.channels.SocketChannel; 11 | import java.util.Locale; 12 | 13 | public class HttpCapturerTunnel extends Tunnel { 14 | private static final String TAG = "HttpCapturerTunnel"; 15 | 16 | public HttpCapturerTunnel(InetSocketAddress serverAddress, Selector selector) throws Exception { 17 | super(serverAddress, selector); 18 | } 19 | 20 | public HttpCapturerTunnel(SocketChannel innerChannel, Selector selector) throws Exception { 21 | super(innerChannel, selector); 22 | } 23 | 24 | @Override 25 | protected void onConnected(ByteBuffer buffer) throws Exception { 26 | onTunnelEstablished(); 27 | } 28 | 29 | @Override 30 | protected void beforeSend(ByteBuffer buffer) throws Exception { 31 | String body = new String(buffer.array()); 32 | if (!body.contains("HTTP")) return; 33 | 34 | // Extract http target from http packet 35 | String[] lines = body.split("\r\n"); 36 | String path = lines[0].split(" ")[1]; 37 | String host = ""; 38 | for (String line : lines) { 39 | if (line.toLowerCase(Locale.ROOT).startsWith("host")) { 40 | host = line.substring(4); 41 | while (host.startsWith(":") || host.startsWith(" ")) { 42 | host = host.substring(1); 43 | } 44 | while (host.endsWith("\n") || host.endsWith("\r") || host.endsWith(" ")) { 45 | host = host.substring(0, host.length() - 1); 46 | } 47 | } 48 | } 49 | if (!path.startsWith("/")) path = "/" + path; 50 | 51 | String url = "http://" + host + path; 52 | Log.d(TAG, "HTTP url: " + url); 53 | 54 | // If it's a auth redirect request, catch it 55 | if (url.startsWith("http://tgk-wcaime.wahlap.com/wc_auth/oauth/callback/maimai-dx")) { 56 | Log.d(TAG, "Auth request caught!"); 57 | CrawlerCaller.INSTANCE.fetchData(url); 58 | } 59 | } 60 | 61 | @Override 62 | protected void afterReceived(ByteBuffer buffer) { 63 | } 64 | 65 | @Override 66 | protected boolean isTunnelEstablished() { 67 | return true; 68 | } 69 | 70 | @Override 71 | protected void onDispose() { 72 | // TODO Auto-generated method stub 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/tunnel/RawTunnel.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.tunnel; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.nio.ByteBuffer; 5 | import java.nio.channels.Selector; 6 | import java.nio.channels.SocketChannel; 7 | 8 | public class RawTunnel extends Tunnel { 9 | 10 | public RawTunnel(InetSocketAddress serverAddress, Selector selector) throws Exception { 11 | super(serverAddress, selector); 12 | } 13 | 14 | public RawTunnel(SocketChannel innerChannel, Selector selector) throws Exception { 15 | super(innerChannel, selector); 16 | // TODO Auto-generated constructor stub 17 | } 18 | 19 | @Override 20 | protected void onConnected(ByteBuffer buffer) throws Exception { 21 | onTunnelEstablished(); 22 | } 23 | 24 | @Override 25 | protected void beforeSend(ByteBuffer buffer) throws Exception { 26 | // TODO Auto-generated method stub 27 | 28 | } 29 | 30 | @Override 31 | protected void afterReceived(ByteBuffer buffer) throws Exception { 32 | // TODO Auto-generated method stub 33 | 34 | } 35 | 36 | @Override 37 | protected boolean isTunnelEstablished() { 38 | return true; 39 | } 40 | 41 | @Override 42 | protected void onDispose() { 43 | // TODO Auto-generated method stub 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/network/vpn/tunnel/httpconnect/HttpConnectConfig.java: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.network.vpn.tunnel.httpconnect; 2 | 3 | import android.net.Uri; 4 | 5 | import com.paperpig.maimaidata.network.vpn.tunnel.Config; 6 | 7 | import java.net.InetSocketAddress; 8 | 9 | public class HttpConnectConfig extends Config { 10 | public String UserName; 11 | public String Password; 12 | 13 | public static HttpConnectConfig parse(String proxyInfo) { 14 | HttpConnectConfig config = new HttpConnectConfig(); 15 | Uri uri = Uri.parse(proxyInfo); 16 | String userInfoString = uri.getUserInfo(); 17 | if (userInfoString != null) { 18 | String[] userStrings = userInfoString.split(":"); 19 | config.UserName = userStrings[0]; 20 | if (userStrings.length >= 2) { 21 | config.Password = userStrings[1]; 22 | } 23 | } 24 | config.ServerAddress = new InetSocketAddress(uri.getHost(), uri.getPort()); 25 | return config; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (o == null) 31 | return false; 32 | return this.toString().equals(o.toString()); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return String.format("http://%s:%s@%s", UserName, Password, ServerAddress); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/repository/AliasRepository.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.repository 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.paperpig.maimaidata.db.dao.AliasDao 5 | import com.paperpig.maimaidata.db.entity.AliasEntity 6 | 7 | class AliasRepository private constructor(private val aliasDao: AliasDao) { 8 | companion object { 9 | @Volatile 10 | private var instance: AliasRepository? = null 11 | fun getInstance(songChartDao: AliasDao): AliasRepository { 12 | if (instance == null) { 13 | instance = AliasRepository(songChartDao) 14 | } 15 | return instance!! 16 | } 17 | } 18 | 19 | /** 20 | * 通过歌曲id搜索别名列表 21 | */ 22 | fun getAliasListBySongId(id: Int): LiveData> { 23 | return aliasDao.getAliasListBySongId(id) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/repository/ChartRepository.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.repository 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.paperpig.maimaidata.db.dao.ChartDao 5 | import com.paperpig.maimaidata.model.MaxNotesStats 6 | 7 | class ChartRepository private constructor(private val chartDao: ChartDao) { 8 | companion object { 9 | @Volatile 10 | private var instance: ChartRepository? = null 11 | fun getInstance(chartDao: ChartDao): ChartRepository { 12 | if (instance == null) { 13 | instance = ChartRepository(chartDao) 14 | } 15 | return instance!! 16 | } 17 | } 18 | 19 | /** 20 | * 获取最大音符数据,用于展示图表 21 | */ 22 | fun getMaxNotes(): LiveData { 23 | return chartDao.getMaxNotes() 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/repository/ChartStatsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.repository 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.paperpig.maimaidata.db.dao.ChartStatsDao 5 | import com.paperpig.maimaidata.db.entity.ChartStatsEntity 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | class ChartStatsRepository private constructor(val chartStatsDao: ChartStatsDao) { 10 | companion object { 11 | @Volatile 12 | private var instance: ChartStatsRepository? = null 13 | 14 | fun getInstance(chartStatsDao: ChartStatsDao): ChartStatsRepository { 15 | return instance ?: synchronized(this) { 16 | instance ?: ChartStatsRepository(chartStatsDao).also { instance = it } 17 | } 18 | } 19 | } 20 | 21 | suspend fun replaceAllChartStats(list: List): Boolean { 22 | return withContext(Dispatchers.IO) { 23 | chartStatsDao.replaceAllChartStats(list) 24 | } 25 | } 26 | 27 | fun getChartStatsBySongIdAndDifficultyIndex( 28 | songId: Int, 29 | index: Int 30 | ): LiveData { 31 | return chartStatsDao.getChartStatsBySongIdAndDifficultyIndex(songId, index) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/repository/RecordRepository.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.repository 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.paperpig.maimaidata.db.dao.RecordDao 5 | import com.paperpig.maimaidata.db.entity.RecordEntity 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | class RecordRepository private constructor(private val recordDao: RecordDao) { 10 | companion object { 11 | @Volatile 12 | private var instance: RecordRepository? = null 13 | 14 | fun getInstance(recordDao: RecordDao): RecordRepository { 15 | if (instance == null) { 16 | instance = RecordRepository(recordDao) 17 | } 18 | return instance!! 19 | } 20 | } 21 | 22 | 23 | /** 24 | * 更新本地成绩数据库 25 | */ 26 | suspend fun replaceAllRecord(list: List): Boolean { 27 | return withContext(Dispatchers.IO) { 28 | recordDao.replaceAllRecord(list) 29 | } 30 | } 31 | 32 | /** 33 | * 获取所有成绩 34 | */ 35 | fun getAllRecord(): LiveData> { 36 | return recordDao.getAllRecords() 37 | } 38 | 39 | /** 40 | * 根据难度索引获取成绩 41 | * @param index 0 = basic , 1 = advance , 2 = expert , 3 = master , 4 = remaster 42 | * @return 成绩列表 43 | */ 44 | fun getRecordsByDifficultyIndex(index: Int): LiveData> { 45 | return recordDao.getRecordsByDifficultyIndex(index) 46 | } 47 | 48 | 49 | /** 50 | * 根据歌曲ID获取成绩 51 | * @param songId 歌曲ID 52 | * @return 成绩列表 53 | */ 54 | fun getRecordsBySongId(songId: Int): LiveData> { 55 | return recordDao.getRecordsBySongId(songId) 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/repository/SongDataRepository.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.repository 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import com.paperpig.maimaidata.model.SongData 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | import java.io.FileInputStream 10 | 11 | class SongDataRepository { 12 | suspend fun getData(context: Context?): List { 13 | return withContext(Dispatchers.IO) { 14 | try { 15 | val fileInputStream: FileInputStream? = 16 | context?.openFileInput("songdata.json") 17 | 18 | val list = fileInputStream?.bufferedReader().use { 19 | it?.readText() 20 | } 21 | Gson().fromJson( 22 | list, object : TypeToken>() {}.type 23 | ) 24 | 25 | } catch (_: Exception) { 26 | emptyList() 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.inputmethod.InputMethodManager 9 | import androidx.fragment.app.Fragment 10 | import androidx.viewbinding.ViewBinding 11 | 12 | 13 | abstract class BaseFragment : Fragment() { 14 | private var binding: ViewBinding? = null 15 | 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | setHasOptionsMenu(true) 19 | super.onCreate(savedInstanceState) 20 | } 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, 24 | container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View? { 27 | binding = getViewBinding(container) 28 | return binding?.root 29 | } 30 | 31 | override fun onDestroyView() { 32 | super.onDestroyView() 33 | binding = null 34 | } 35 | 36 | protected abstract fun getViewBinding(container: ViewGroup?): T 37 | 38 | fun hideKeyboard(view: View?) { 39 | val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) 40 | if (imm is InputMethodManager) { 41 | imm.hideSoftInputFromWindow(view?.windowToken, 0) 42 | } 43 | } 44 | 45 | 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/about/AboutFragment.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.about 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import androidx.preference.Preference 7 | import androidx.preference.PreferenceFragmentCompat 8 | import com.paperpig.maimaidata.BuildConfig 9 | import com.paperpig.maimaidata.R 10 | import com.paperpig.maimaidata.utils.SpUtil 11 | import java.text.SimpleDateFormat 12 | import java.util.Locale 13 | 14 | 15 | class AboutFragment : PreferenceFragmentCompat() { 16 | companion object { 17 | const val PROJECT_URL = "https://github.com/PaperPig/MaimaiData" 18 | const val FEEDBACK_URL = "https://github.com/PaperPig/MaimaiData/issues" 19 | } 20 | 21 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 22 | setPreferencesFromResource(R.xml.abot_preferences, rootKey) 23 | 24 | 25 | 26 | findPreference("version")?.summary = BuildConfig.VERSION_NAME 27 | findPreference("base_data_version")?.summary = SpUtil.getDataVersion() 28 | findPreference("last_time_update_chart_stats")?.summary = 29 | SpUtil.getLastUpdateChartStats().let { 30 | SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(it) 31 | } 32 | findPreference("project_url")?.apply { 33 | summary = PROJECT_URL 34 | setOnPreferenceClickListener { 35 | openUrl(PROJECT_URL) 36 | true 37 | } 38 | } 39 | findPreference("feedback")?.apply { 40 | summary = FEEDBACK_URL 41 | setOnPreferenceClickListener { 42 | openUrl(FEEDBACK_URL) 43 | true 44 | } 45 | } 46 | } 47 | 48 | private fun openUrl(url: String) { 49 | startActivity(Intent(Intent.ACTION_VIEW).apply { 50 | data = Uri.parse(url) 51 | }) 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/about/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.about 2 | 3 | import android.os.Bundle 4 | import android.view.MenuItem 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.paperpig.maimaidata.R 7 | import com.paperpig.maimaidata.databinding.ActivitySettingsBinding 8 | 9 | class SettingsActivity : AppCompatActivity() { 10 | 11 | 12 | private lateinit var binding: ActivitySettingsBinding 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = ActivitySettingsBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | if (savedInstanceState == null) { 19 | supportFragmentManager 20 | .beginTransaction() 21 | .replace(R.id.about_container, SettingsFragment()) 22 | .commit() 23 | } 24 | 25 | setSupportActionBar(binding.toolbarLayout.toolbar) 26 | supportActionBar?.apply { 27 | setTitle(R.string.settings) 28 | setDisplayHomeAsUpEnabled(true) 29 | } 30 | } 31 | 32 | 33 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 34 | if (item.itemId == android.R.id.home) finish() 35 | return true 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/about/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.about 2 | 3 | import android.os.Bundle 4 | import androidx.preference.EditTextPreference 5 | import androidx.preference.PreferenceFragmentCompat 6 | import com.paperpig.maimaidata.R 7 | 8 | class SettingsFragment : PreferenceFragmentCompat() { 9 | 10 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 11 | setPreferencesFromResource(R.xml.settings_preferences, rootKey) 12 | findPreference("nickname")?.summaryProvider = 13 | EditTextPreference.SimpleSummaryProvider.getInstance() 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/checklist/LevelArrayAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.checklist 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ArrayAdapter 8 | import android.widget.TextView 9 | import com.paperpig.maimaidata.R 10 | 11 | 12 | class LevelArrayAdapter(context: Context?, resource: Int, val list: List?) : 13 | ArrayAdapter(context!!, resource, list!!) { 14 | 15 | 16 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { 17 | return initView(position, convertView, parent) 18 | } 19 | 20 | override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { 21 | return initView(position, convertView, parent) 22 | } 23 | 24 | private fun initView(position: Int, convertView: View?, parent: ViewGroup): View { 25 | var view = convertView 26 | if (view == null) { 27 | view = 28 | LayoutInflater.from(context).inflate(R.layout.item_spinner_level, parent, false) 29 | 30 | } 31 | val levelName = view!!.findViewById(R.id.levelName) 32 | levelName.text = getItem(position) 33 | return view 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/checklist/VersionArrayAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.checklist 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ArrayAdapter 8 | import android.widget.ImageView 9 | import android.widget.TextView 10 | import com.bumptech.glide.Glide 11 | import com.paperpig.maimaidata.R 12 | import com.paperpig.maimaidata.model.Version 13 | 14 | 15 | class VersionArrayAdapter(context: Context?, resource: Int, val list: List?) : 16 | ArrayAdapter(context!!, resource, list!!) { 17 | 18 | 19 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { 20 | return initView(position, convertView, parent) 21 | } 22 | 23 | override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { 24 | return initView(position, convertView, parent) 25 | } 26 | 27 | private fun initView(position: Int, convertView: View?, parent: ViewGroup): View { 28 | 29 | var view = convertView 30 | if (view == null) { 31 | view = 32 | LayoutInflater.from(context).inflate(R.layout.item_spinner_version, parent, false) 33 | 34 | } 35 | val versionImage = view!!.findViewById(R.id.versionImage) 36 | val versionName = view.findViewById(R.id.versionName) 37 | Glide.with(context).load(getItem(position)?.res).into(versionImage) 38 | versionName.text = getItem(position)?.versionName 39 | return view 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/maimaidxprober/AccountAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.maimaidxprober 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | class AccountAdapter( 10 | private val accounts: List, 11 | private val onItemClick: (String) -> Unit, 12 | ) : RecyclerView.Adapter() { 13 | 14 | inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { 15 | val textView: TextView = view.findViewById(android.R.id.text1) 16 | init { 17 | view.setOnClickListener { 18 | val account = accounts[getBindingAdapterPosition()] 19 | onItemClick(account) 20 | } 21 | } 22 | } 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 25 | val v = LayoutInflater.from(parent.context) 26 | .inflate(android.R.layout.simple_list_item_1, parent, false) 27 | return ViewHolder(v) 28 | } 29 | 30 | override fun getItemCount(): Int = accounts.size 31 | 32 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 33 | holder.textView.text = accounts[position] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/maimaidxprober/AccountListBottomSheet.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.maimaidxprober 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 9 | import com.paperpig.maimaidata.R 10 | import com.paperpig.maimaidata.databinding.FragmentAccountListBottomSheetBinding 11 | 12 | class AccountListBottomSheet( 13 | private val accounts: List, 14 | private val onAccountSelected: (String) -> Unit, 15 | private val onAccountDeleted: (String) -> Unit 16 | ) : BottomSheetDialogFragment() { 17 | 18 | override fun getTheme(): Int { 19 | return R.style.Theme_Material3_Light_BottomSheetDialog 20 | } 21 | 22 | private var deleteMode = false 23 | private lateinit var binding: FragmentAccountListBottomSheetBinding 24 | 25 | override fun onCreateView( 26 | inflater: LayoutInflater, 27 | container: ViewGroup?, 28 | savedInstanceState: Bundle?, 29 | ): View? { 30 | binding = FragmentAccountListBottomSheetBinding.inflate(layoutInflater, container, false) 31 | 32 | binding.accountRecyclerView.apply { 33 | layoutManager = LinearLayoutManager(context) 34 | adapter = AccountAdapter( 35 | accounts, 36 | onItemClick = { selected -> 37 | if (!deleteMode) { 38 | onAccountSelected(selected) 39 | dismiss() 40 | } else { 41 | onAccountDeleted(selected) 42 | dismiss() 43 | } 44 | }, 45 | ) 46 | } 47 | 48 | binding.deleteModeButton.setOnClickListener { 49 | deleteMode = !deleteMode 50 | binding.titleText.text = if (deleteMode) "删除账号" else "选择账号" 51 | } 52 | 53 | return binding.root 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/maimaidxprober/ProberVersionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.maimaidxprober 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.LinearLayoutManager 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.paperpig.maimaidata.db.entity.RecordEntity 7 | import com.paperpig.maimaidata.db.entity.SongWithChartsEntity 8 | 9 | class ProberVersionAdapter(dataList: List) : 10 | RecyclerView.Adapter() { 11 | private var recordList = listOf() 12 | private var isDataMatching = true 13 | private var b35Adapter: RecordAdapter = RecordAdapter(dataList) 14 | private var b15Adapter: RecordAdapter = RecordAdapter(dataList) 15 | 16 | class ViewHolder(val recyclerView: RecyclerView) : RecyclerView.ViewHolder(recyclerView) 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 19 | val recyclerView = RecyclerView(parent.context).apply { 20 | layoutParams = ViewGroup.LayoutParams( 21 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT 22 | ) 23 | } 24 | return ViewHolder(recyclerView) 25 | } 26 | 27 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 28 | holder.recyclerView.apply { 29 | adapter = if (position == 0) { 30 | b35Adapter 31 | } else b15Adapter 32 | layoutManager = LinearLayoutManager(holder.itemView.context) 33 | 34 | } 35 | } 36 | 37 | override fun getItemCount(): Int { 38 | return 2 39 | } 40 | 41 | fun setData(data: List) { 42 | recordList = data 43 | b35Adapter.setData(recordList, 0) 44 | b15Adapter.setData(recordList, 1) 45 | isDataMatching = b35Adapter.isMatching && b15Adapter.isMatching 46 | notifyDataSetChanged() 47 | } 48 | 49 | fun isDataMatching(): Boolean = isDataMatching 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/rating/ProberUpdateDialog.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.rating 2 | 3 | import android.content.Context 4 | import com.google.android.material.bottomsheet.BottomSheetDialog 5 | import com.paperpig.maimaidata.R 6 | import com.paperpig.maimaidata.databinding.DialogProberUpdateBinding 7 | 8 | class ProberUpdateDialog(context: Context) : 9 | BottomSheetDialog(context, R.style.Theme_Material3_Light_BottomSheetDialog) { 10 | private val binding: DialogProberUpdateBinding = 11 | DialogProberUpdateBinding.inflate(layoutInflater) 12 | 13 | init { 14 | setContentView(binding.root) 15 | setCanceledOnTouchOutside(true) 16 | setCancelable(true) 17 | } 18 | 19 | 20 | fun clearText() { 21 | binding.proberUpdateStatusText.text = "" 22 | } 23 | 24 | fun appendText(text: String) { 25 | binding.proberUpdateStatusText.append(text) 26 | } 27 | 28 | override fun show() { 29 | super.show() 30 | binding.proberUpdateStatusScroll.post { 31 | binding.proberUpdateStatusScroll.scrollTo(0, binding.proberUpdateStatusText.bottom) 32 | } 33 | } 34 | 35 | 36 | override fun onStart() { 37 | super.onStart() 38 | val window = window 39 | if (window != null) { 40 | val layoutParams = window.attributes 41 | layoutParams.width = context.resources.displayMetrics.widthPixels 42 | window.attributes = layoutParams 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/ui/rating/RatingResultAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.ui.rating 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import android.widget.TextView 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.paperpig.maimaidata.databinding.ItemRatingReusltBinding 8 | import com.paperpig.maimaidata.model.Rating 9 | 10 | class RatingResultAdapter : RecyclerView.Adapter() { 11 | private var data = listOf() 12 | 13 | inner class ViewHolder(binding: ItemRatingReusltBinding) : 14 | RecyclerView.ViewHolder(binding.root) { 15 | val innerLevel: TextView = binding.innerLevel 16 | val achievement: TextView = binding.achievement 17 | val rating: TextView = binding.rating 18 | val totalRating: TextView = binding.totalRating 19 | } 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 22 | 23 | return ViewHolder( 24 | ItemRatingReusltBinding.inflate( 25 | LayoutInflater.from(parent.context), parent, false 26 | ) 27 | ) 28 | } 29 | 30 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 31 | 32 | val ratingModel = data[position] 33 | 34 | holder.innerLevel.text = ratingModel.innerLevel.toString() 35 | holder.achievement.text = ratingModel.achi 36 | holder.rating.text = ratingModel.rating.toString() 37 | holder.totalRating.text = ratingModel.total.toString() 38 | 39 | } 40 | 41 | override fun getItemCount(): Int { 42 | return data.size 43 | } 44 | 45 | fun setData(list: List) { 46 | data = list 47 | notifyDataSetChanged() 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.utils 2 | 3 | object Constants { 4 | const val GENRE_UTAGE = "宴会場" 5 | const val CHART_TYPE_DX = "DX" 6 | const val CHART_TYPE_SD = "SD" 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/utils/ConvertUtils.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.utils 2 | 3 | object ConvertUtils { 4 | 5 | /** 6 | * 通过定数和达成率计算单曲rating 7 | */ 8 | fun achievementToRating(level: Int, achi: Int): Int { 9 | val i = when { 10 | achi >= 1005000 -> { 11 | 22.4 12 | } 13 | 14 | achi == 1004999 -> { 15 | 22.2 16 | } 17 | 18 | achi >= 1000000 -> { 19 | 21.6 20 | } 21 | 22 | achi == 999999 -> { 23 | 21.4 24 | } 25 | 26 | achi >= 995000 -> { 27 | 21.1 28 | } 29 | 30 | achi >= 990000 -> { 31 | 20.8 32 | } 33 | 34 | achi >= 980000 -> { 35 | 20.3 36 | } 37 | 38 | achi >= 970000 -> { 39 | 20.0 40 | } 41 | 42 | achi >= 940000 -> { 43 | 16.8 44 | } 45 | 46 | achi >= 900000 -> { 47 | 15.2 48 | } 49 | 50 | achi >= 800000 -> { 51 | 13.6 52 | } 53 | 54 | achi >= 750000 -> { 55 | 12.0 56 | } 57 | 58 | achi >= 700000 -> { 59 | 11.2 60 | } 61 | 62 | achi >= 600000 -> { 63 | 9.6 64 | } 65 | 66 | achi >= 500000 -> { 67 | 8.0 68 | } 69 | 70 | else -> 0.0 71 | } 72 | 73 | 74 | val temp = achi.coerceAtMost(1005000) * level * i 75 | return (temp / 10000000).toInt() 76 | } 77 | 78 | 79 | fun getLevel(levelText: String): String { 80 | return when (levelText) { 81 | "LEVEL 1" -> return "1" 82 | "LEVEL 2" -> return "2" 83 | "LEVEL 3" -> return "3" 84 | "LEVEL 4" -> return "4" 85 | "LEVEL 5" -> return "5" 86 | "LEVEL 6" -> return "6" 87 | "LEVEL 7" -> return "7" 88 | "LEVEL 7+" -> return "7+" 89 | "LEVEL 8" -> return "8" 90 | "LEVEL 8+" -> return "8+" 91 | "LEVEL 9" -> return "9" 92 | "LEVEL 9+" -> return "9+" 93 | "LEVEL 10" -> return "10" 94 | "LEVEL 10+" -> return "10+" 95 | "LEVEL 11" -> return "11" 96 | "LEVEL 11+" -> return "11+" 97 | "LEVEL 12" -> return "12" 98 | "LEVEL 12+" -> return "12+" 99 | "LEVEL 13" -> return "13" 100 | "LEVEL 13+" -> return "13+" 101 | "LEVEL 14" -> return "14" 102 | "LEVEL 14+" -> return "14+" 103 | "LEVEL 15" -> return "15" 104 | else -> "0" 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.utils 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | import android.content.res.Resources 7 | import android.util.TypedValue 8 | import android.view.MotionEvent 9 | import android.view.View 10 | import android.widget.Toast 11 | 12 | fun String.getInt(): Int { 13 | return if (this.isEmpty()) { 14 | 0 15 | } else { 16 | this.toInt() 17 | } 18 | } 19 | 20 | fun List.versionCheck(string: String): Boolean { 21 | for (i in this) { 22 | if (i == "maimai") { 23 | if (string == "maimai" || string == "maimai PLUS") return true 24 | } else if (i == "舞萌DX") { 25 | if (string == "舞萌DX") return true 26 | } else if (string.contains(i)) return true 27 | } 28 | return false 29 | } 30 | 31 | fun Int.toDp(): Float { 32 | return TypedValue.applyDimension( 33 | TypedValue.COMPLEX_UNIT_DIP, 34 | this.toFloat(), 35 | Resources.getSystem().displayMetrics 36 | ) 37 | } 38 | 39 | fun View.setDebouncedClickListener(debounceTime: Long = 2000L, action: (view: View) -> Unit) { 40 | var lastClickTime = 0L 41 | this.setOnClickListener { view -> 42 | val currentTime = System.currentTimeMillis() 43 | if (currentTime - lastClickTime >= debounceTime) { 44 | action(view) 45 | } 46 | lastClickTime = currentTime 47 | } 48 | } 49 | 50 | fun View.setShrinkOnTouch( 51 | scale: Float = 0.9f, 52 | duration: Long = 100L, 53 | keepLongClick: Boolean = false 54 | ) { 55 | setOnTouchListener { v, event -> 56 | when (event.action) { 57 | MotionEvent.ACTION_DOWN -> { 58 | v.animate() 59 | .scaleX(scale) 60 | .scaleY(scale) 61 | .setDuration(duration) 62 | .start() 63 | } 64 | MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { 65 | v.animate() 66 | .scaleX(1f) 67 | .scaleY(1f) 68 | .setDuration(duration) 69 | .start() 70 | } 71 | } 72 | keepLongClick 73 | } 74 | } 75 | 76 | fun View.setCopyOnLongClick( 77 | textToCopy: String, 78 | label: String = "Copied Text", 79 | copiedMessage: String = "已复制:$textToCopy", 80 | errorMessage: String = "无法访问剪贴板" 81 | ) { 82 | setOnLongClickListener { 83 | val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager 84 | if (clipboard != null) { 85 | val clip = ClipData.newPlainText(label, textToCopy) 86 | clipboard.setPrimaryClip(clip) 87 | Toast.makeText(context, copiedMessage, Toast.LENGTH_SHORT).show() 88 | } else { 89 | Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() 90 | } 91 | true 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/utils/PermissionHelper.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.utils 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import androidx.activity.result.ActivityResultLauncher 8 | import androidx.core.app.ActivityCompat 9 | 10 | /** 11 | * 权限工具类 12 | */ 13 | class PermissionHelper private constructor(private val activity: Activity) { 14 | 15 | companion object { 16 | fun with(activity: Activity): PermissionHelper { 17 | return PermissionHelper(activity) 18 | } 19 | } 20 | 21 | private var mPermissionLauncher: ActivityResultLauncher>? = null 22 | private lateinit var permissionCallback: PermissionCallback 23 | 24 | 25 | interface PermissionCallback { 26 | fun onAllGranted() 27 | fun onDenied(deniedPermissions: List) 28 | } 29 | 30 | fun registerLauncher(launcher: ActivityResultLauncher>): PermissionHelper { 31 | mPermissionLauncher = launcher 32 | return this 33 | } 34 | 35 | 36 | fun checkStoragePermission(callback: PermissionCallback) { 37 | permissionCallback = callback 38 | val permissionsStorage = 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 40 | // Android 13+ 请求细粒度权限 41 | arrayOf(Manifest.permission.READ_MEDIA_IMAGES) 42 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 43 | // Android 10-12 请求旧权限 44 | arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) 45 | } else { 46 | // Android 10以下权限请求 47 | arrayOf( 48 | Manifest.permission.READ_EXTERNAL_STORAGE, 49 | Manifest.permission.WRITE_EXTERNAL_STORAGE 50 | ) 51 | } 52 | 53 | if (!permissionsStorage.all { permission -> 54 | ActivityCompat.checkSelfPermission( 55 | activity, 56 | permission 57 | ) == PackageManager.PERMISSION_GRANTED 58 | } 59 | ) { 60 | mPermissionLauncher?.launch(permissionsStorage) 61 | ?: throw IllegalStateException("Permission launcher is not registered. Please call registerLauncher() before requesting permissions.") 62 | } else { 63 | permissionCallback.onAllGranted() 64 | } 65 | } 66 | 67 | fun onRequestPermissionsResult(map: Map) { 68 | val denied = map.filterValues { !it }.keys.toList() 69 | if (denied.isEmpty()) { 70 | permissionCallback.onAllGranted() 71 | } else { 72 | permissionCallback.onDenied(denied) 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/utils/WindowsUtils.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.utils 2 | 3 | import android.content.Context 4 | 5 | object WindowsUtils { 6 | fun getWindowWidth(context: Context?): Float { 7 | val dm = context?.resources?.displayMetrics ?: return 0F 8 | return dm.widthPixels.toFloat() 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/widgets/Settings.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.widgets 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.preference.PreferenceManager 6 | 7 | 8 | object Settings { 9 | private lateinit var settingsPre: SharedPreferences 10 | 11 | fun init(context: Context) { 12 | if (!::settingsPre.isInitialized) { 13 | settingsPre = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) 14 | } 15 | } 16 | 17 | // 配置项键值对 18 | private const val KEY_ALIAS_SEARCH = "enable_alias_search" 19 | private const val DEFAULT_ALIAS_SEARCH = true 20 | 21 | private const val KEY_CHARTER_SEARCH = "enable_charter_search" 22 | private const val DEFAULT_CHARTER_SEARCH = false 23 | 24 | private const val KEY_SHOW_ALIAS = "enable_show_alias" 25 | private const val DEFAULT_SHOW_ALIAS = true 26 | 27 | private const val KEY_USE_DIVING_FISH_NICKNAME = "enable_diving_fish_nickname" 28 | private const val DEFAULT_USE_DIVING_FISH_NICKNAME = true 29 | 30 | private const val KEY_NICKNAME = "nickname" 31 | private const val DEFAULT_NICKNAME = "" 32 | 33 | private const val KEY_SELECT_DIFFICULTIES = "select_difficulties" 34 | private val DEFAULT_SELECT = setOf("2", "3", "4") 35 | 36 | 37 | fun getEnableAliasSearch() = 38 | settingsPre.getBoolean(KEY_ALIAS_SEARCH, DEFAULT_ALIAS_SEARCH) 39 | 40 | fun getEnableCharterSearch() = 41 | settingsPre.getBoolean(KEY_CHARTER_SEARCH, DEFAULT_CHARTER_SEARCH) 42 | 43 | fun getEnableShowAlias() = 44 | settingsPre.getBoolean(KEY_SHOW_ALIAS, DEFAULT_SHOW_ALIAS) 45 | 46 | fun getEnableDivingFishNickname() = 47 | settingsPre.getBoolean(KEY_USE_DIVING_FISH_NICKNAME, DEFAULT_USE_DIVING_FISH_NICKNAME) 48 | 49 | fun getNickname(): String = 50 | settingsPre.getString(KEY_NICKNAME, DEFAULT_NICKNAME) ?: DEFAULT_NICKNAME 51 | 52 | fun getUpdateDifficulty(): Set { 53 | val selectedSet = 54 | settingsPre.getStringSet(KEY_SELECT_DIFFICULTIES, DEFAULT_SELECT) ?: DEFAULT_SELECT 55 | return selectedSet.mapNotNull { it.toIntOrNull() }.toSet() 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/paperpig/maimaidata/widgets/TilingImageView.kt: -------------------------------------------------------------------------------- 1 | package com.paperpig.maimaidata.widgets 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import android.graphics.BitmapShader 8 | import android.graphics.Canvas 9 | import android.graphics.Paint 10 | import android.graphics.Shader 11 | import android.util.AttributeSet 12 | import android.view.View 13 | import android.view.animation.LinearInterpolator 14 | import com.paperpig.maimaidata.R 15 | 16 | class TilingImageView(context: Context, attrs: AttributeSet) : View(context, attrs) { 17 | 18 | private var shader: BitmapShader? = null 19 | private val paint = Paint() 20 | private var offsetX = 0f 21 | private var offsetY = 0f 22 | private var animator:ValueAnimator? = null 23 | 24 | init { 25 | val bitmap = BitmapFactory.decodeResource(resources, R.drawable.mmd_main_bg_pattern) 26 | shader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) 27 | paint.shader = shader 28 | 29 | // 使用 ValueAnimator 实现滚动效果 30 | animator = ValueAnimator.ofFloat(0f, 1f).apply { 31 | duration = 50000 32 | repeatMode = ValueAnimator.RESTART 33 | repeatCount = ValueAnimator.INFINITE 34 | interpolator = LinearInterpolator() 35 | addUpdateListener { 36 | val progress = it.animatedValue as Float 37 | offsetX = progress * width 38 | offsetY = (1f - progress) * height 39 | invalidate() // 重绘 View 40 | } 41 | } 42 | animator?.start() 43 | } 44 | 45 | override fun onDraw(canvas: Canvas) { 46 | super.onDraw(canvas) 47 | canvas.save() 48 | canvas.translate(-offsetX, -offsetY) 49 | canvas.drawPaint(paint) 50 | canvas.restore() 51 | } 52 | 53 | fun pauseAnimation() { 54 | animator?.pause() 55 | } 56 | 57 | fun resumeAnimation() { 58 | animator?.resume() 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_search.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-hdpi/ic_menu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-hdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-mdpi/ic_menu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-mdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xhdpi/ic_menu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_deluxe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_deluxe.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_list.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_menu.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_rating.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_standard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_tips.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_transform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/ic_transform.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/icon_on.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_finale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_finale.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_green_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_green_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_milk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_milk.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_milk_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_milk_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_murasaki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_murasaki.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_murasaki_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_murasaki_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_orange.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_orange_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_orange_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_pink.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_pink_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_pink_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimai_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimai_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_2021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_2021.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_2022.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_2023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_2023.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_2024.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_buddies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_buddies.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_buddies_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_buddies_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_cn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_festival.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_festival.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_festival_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_festival_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_splash_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_splash_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_universe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_universe.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/maimaidx_universe_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/maimaidx_universe_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_chara_sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_chara_sp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_checklist_top_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_checklist_top_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_cover_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_cover_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_cover_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_cover_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_delete_search_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_delete_search_history.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_favorite_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_favorite_checked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_laundry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_laundry.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_back_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_back_center.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_back_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_back_left.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_back_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_back_right.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_front_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_front_center.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_front_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_front_left.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_front_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_front_right.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_moon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_rainbow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_main_bg_rainbow_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_main_bg_rainbow_bottom.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_best50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_best50.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_name_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_name_box.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_6.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_7.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_8.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_num_drating_9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rating_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rating_box.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_a.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_aa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_aa.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_aaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_aaa.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_ap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_ap.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_bb.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_bbb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_bbb.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_c.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_d.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_adv.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_adv.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_bsc.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_bsc.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_exp.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_exp.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_mst.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_mst.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_rem.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_diff_rem.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fcp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fs.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fsd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fsd.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fsdp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fsdp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_fsp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_icon_dx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_icon_dx.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_icon_standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_icon_standard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_level_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_level_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_rating_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_rating_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_s.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_sp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_ss.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_ssp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_ssp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_sss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_sss.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_sssp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_sssp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_stub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_player_rtsong_stub.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_board_adv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_board_adv.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_board_bsc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_board_bsc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_board_exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_board_exp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_board_mas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_board_mas.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_board_rem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_board_rem.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_diff_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_diff_advanced.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_diff_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_diff_basic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_diff_expert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_diff_expert.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_diff_master.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_diff_master.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_diff_remaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_diff_remaster.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_blue.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_bronze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_bronze.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_gold.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_green.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_normal.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_orange.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_platinum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_platinum.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_purple.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_rainbow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_red.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_rating_plate_silver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_rating_plate_silver.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_record_divider.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_record_divider.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_shooting_star_head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_shooting_star_head.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_shooting_star_tail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_shooting_star_tail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_song_jacket_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_song_jacket_placeholder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/mmd_song_utage_party.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable-xxhdpi/mmd_song_utage_party.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_percent_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/eye_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/eye_closed.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/eye_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/eye_open.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_advanced_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_basic_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_expert_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_master_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_remaster_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/level_utage_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_chuni_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_chuni_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_maimai_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_maimai_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_nico_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_nico_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_pop_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_pop_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_touhou_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_touhou_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_utage_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_utage_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_variety_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_genre_variety_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_unchecked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_version_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_checkbox_version_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_main_bg_aurora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/mmd_main_bg_aurora.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_main_bg_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_main_bg_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/mmd_main_bg_pattern.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_main_bg_shines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/mmd_main_bg_shines.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_main_bg_star_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/mmd_main_bg_star_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_main_bg_star_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/drawable/mmd_main_bg_star_yellow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_player_rtsong_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_player_rtsong_container_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_player_rtsong_jacket_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_player_rtsong_other_info_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_player_rtsong_title_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_search_lens.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_song_alias_info_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mmd_textview_search_history_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_bar_text_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/prober_version_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_bar_button_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_bar_left_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_bar_right_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_checkbox_checked_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/song_list_genre_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/song_list_info_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/song_note_achievement_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/song_note_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v28/item_search_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 17 | 18 | 19 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_pinch_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 23 | 24 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_version_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 15 | 16 | 17 | 22 | 23 | 29 | 30 |