├── .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 |
37 |
38 |
39 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_prober_update.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_account_list_bottom_sheet.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
13 |
14 |
21 |
22 |
29 |
30 |
31 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_song_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_dots.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_level_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_rating_reuslt.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
27 |
28 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_search_history.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_song_check.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_spinner_level.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_spinner_version.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/mmd_player_rtsong_divider_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
28 |
29 |
30 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/title.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/about_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/share_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #125BB8
4 | #0B3871
5 | #125BB8
6 |
7 | #ffffff
8 | #000000
9 | #8B8B8B
10 | #6FD43D
11 | #F8B709
12 | #FF818D
13 | #9F51DC
14 | #CB5EEA
15 | #B12876
16 | #053264
17 | #E0E0E0
18 | #333333
19 | #EDD2F1
20 |
21 |
22 | #FF9C24
23 | #FFD21F
24 | #12B4FF
25 | #7ED6FF
26 | #CA6FEB
27 | #E8A7FF
28 | #6AE03F
29 | #BAFBA3
30 | #FF4646
31 | #FFA7A7
32 | #319DF8
33 | #99D1FF
34 | #DC39B8
35 | #B12876
36 |
37 |
38 | #F75EA3
39 | #2FCA4C
40 | #868686
41 | #0B3871
42 |
43 | #6FE163
44 | #025235
45 | #F8DF3B
46 | #C7450C
47 | #FF818D
48 | #C02138
49 | #C27FF4
50 | #671B8D
51 | #E5DDEA
52 | #8C2DD5
53 |
54 | #FF4646
55 | #2FCA4C
56 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
18 |
21 |
22 |
32 |
33 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/abot_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
17 |
18 |
22 |
23 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
14 |
15 |
20 |
21 |
26 |
27 |
33 |
34 |
42 |
43 |
48 |
49 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | val kotlinVersion by extra("2.1.0")
4 |
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath ("com.android.tools.build:gradle:8.9.0")
11 | classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | mavenCentral()
22 | }
23 | }
24 |
25 | tasks.register("clean") {
26 | delete(rootProject.layout.buildDirectory)
27 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | org.gradle.unsafe.configuration-cache=true
23 | android.defaults.buildfeatures.buildconfig=true
24 | android.nonTransitiveRClass=false
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaperPig/MaimaiData/e8a6110650ec2c6286e55360cabdecee08bece4b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jul 14 10:20:05 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
9 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":app")
2 | rootProject.name = "MaimaiData"
--------------------------------------------------------------------------------