Showing preview only (655K chars total). Download the full file or copy to clipboard to get everything.
Repository: keluokeda/hs_tracker
Branch: master
Commit: b4c42c2ecf38
Files: 279
Total size: 562.9 KB
Directory structure:
gitextract_40etbzh8/
├── .gitignore
├── 123456
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── release/
│ │ ├── app-1-3-1.apk
│ │ └── output-metadata.json
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── ke/
│ └── hs_tracker/
│ └── app/
│ ├── App.kt
│ └── MainActivity.kt
├── build.gradle
├── core/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs_tracker/
│ └── core/
│ ├── api/
│ │ └── HearthStoneJsonApi.kt
│ ├── entity/
│ │ ├── BlockType.kt
│ │ ├── Card.kt
│ │ ├── CardClass.kt
│ │ ├── CurrentDeck.kt
│ │ ├── Entity.kt
│ │ ├── GameCardType.kt
│ │ ├── InsertStackResult.kt
│ │ ├── LogType.kt
│ │ ├── Mechanics.kt
│ │ ├── NestedTag.kt
│ │ ├── PowerTag.kt
│ │ └── Zone.kt
│ ├── extensions.kt
│ └── parser/
│ ├── BlockTagStack.kt
│ ├── DeckFileObserver.kt
│ ├── PowerFileObserver.kt
│ └── PowerParser.kt
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── libs.versions.toml
├── module/
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ ├── schemas/
│ │ └── com.ke.hs_tracker.module.db.Database/
│ │ ├── 1.json
│ │ ├── 2.json
│ │ ├── 3.json
│ │ └── 4.json
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── ke/
│ │ └── hs_tracker/
│ │ └── module/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/
│ │ │ ├── Decks.log
│ │ │ ├── Power.log
│ │ │ └── log.config
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── ke/
│ │ │ └── hs_tracker/
│ │ │ └── module/
│ │ │ ├── MainApplication.kt
│ │ │ ├── api/
│ │ │ │ └── HearthStoneJsonApi.kt
│ │ │ ├── data/
│ │ │ │ └── PreferenceStorage.kt
│ │ │ ├── db/
│ │ │ │ ├── CardClassesConvert.kt
│ │ │ │ ├── CardDao.kt
│ │ │ │ ├── Database.kt
│ │ │ │ ├── Game.kt
│ │ │ │ ├── GameDao.kt
│ │ │ │ ├── MechanicsListConvert.kt
│ │ │ │ ├── ZonePositionChangedEvent.kt
│ │ │ │ └── ZonePositionChangedEventDao.kt
│ │ │ ├── di/
│ │ │ │ ├── CoroutinesModule.kt
│ │ │ │ ├── CoroutinesQualifiers.kt
│ │ │ │ ├── LogFileDirQualifiers.kt
│ │ │ │ └── Module.kt
│ │ │ ├── domain/
│ │ │ │ ├── ClearCardTableUseCase.kt
│ │ │ │ ├── GetAllCardUseCase.kt
│ │ │ │ ├── GetCardListUseCase.kt
│ │ │ │ ├── GetDatabaseCardCountUseCase.kt
│ │ │ │ ├── GetLocalLogDirUseCase.kt
│ │ │ │ ├── GetRealLogDirUseCase.kt
│ │ │ │ ├── GetSaveLogFileEnableUseCase.kt
│ │ │ │ ├── InsertCardListToDatabaseUseCase.kt
│ │ │ │ ├── ParseDeckCodeUseCase.kt
│ │ │ │ ├── SaveLogFileUseCase.kt
│ │ │ │ ├── SetSaveLogFileEnableUseCase.kt
│ │ │ │ └── WriteLogConfigFileUseCase.kt
│ │ │ ├── entity/
│ │ │ │ ├── BlockType.kt
│ │ │ │ ├── Card.kt
│ │ │ │ ├── CardBean.kt
│ │ │ │ ├── CardClass.kt
│ │ │ │ ├── CardType.kt
│ │ │ │ ├── CurrentDeck.kt
│ │ │ │ ├── Entity.kt
│ │ │ │ ├── EntityWithPayload.kt
│ │ │ │ ├── EnumMoshiAdapter.kt
│ │ │ │ ├── FormatType.kt
│ │ │ │ ├── GameCardType.kt
│ │ │ │ ├── GameEvent.kt
│ │ │ │ ├── GameType.kt
│ │ │ │ ├── GraveyardCard.kt
│ │ │ │ ├── InsertStackResult.kt
│ │ │ │ ├── LogType.kt
│ │ │ │ ├── Mechanics.kt
│ │ │ │ ├── NestedTag.kt
│ │ │ │ ├── PowerTag.kt
│ │ │ │ ├── Race.kt
│ │ │ │ ├── Rarity.kt
│ │ │ │ ├── SpellSchool.kt
│ │ │ │ ├── Turn.kt
│ │ │ │ ├── Zone.kt
│ │ │ │ └── ZoneCard.kt
│ │ │ ├── parser/
│ │ │ │ ├── BlockTagStack.kt
│ │ │ │ ├── DeckCardObserver.kt
│ │ │ │ ├── DeckFileObserver.kt
│ │ │ │ ├── PowerFileObserver.kt
│ │ │ │ ├── PowerParser.kt
│ │ │ │ └── PowerTagHandler.kt
│ │ │ ├── service/
│ │ │ │ ├── ItemViewTouchListener.kt
│ │ │ │ ├── ScaleTouchListener.kt
│ │ │ │ └── WindowService.kt
│ │ │ └── ui/
│ │ │ ├── chart/
│ │ │ │ ├── GetSummaryChartViewDataUseCase.kt
│ │ │ │ ├── PieChartData.kt
│ │ │ │ ├── SummaryChartActivity.kt
│ │ │ │ └── SummaryChartViewData.kt
│ │ │ ├── classbattledetail/
│ │ │ │ ├── ClassBattleDetailActivity.kt
│ │ │ │ ├── ClassBattleDetailViewModel.kt
│ │ │ │ ├── ClassBattleItem.kt
│ │ │ │ └── GetClassBattleItemListUseCase.kt
│ │ │ ├── common/
│ │ │ │ ├── CardAdapter.kt
│ │ │ │ └── LoadingFragment.kt
│ │ │ ├── deck/
│ │ │ │ ├── DeckCodeParserActivity.kt
│ │ │ │ └── DeckCodeParserViewModel.kt
│ │ │ ├── deckbattledetail/
│ │ │ │ ├── BattleRecordsFragment.kt
│ │ │ │ ├── BattleRecordsViewModel.kt
│ │ │ │ ├── DeckBattleDetailActivity.kt
│ │ │ │ ├── DeckDetailFragment.kt
│ │ │ │ ├── DeckDetailViewModel.kt
│ │ │ │ ├── DeckFragment.kt
│ │ │ │ ├── DeckViewModel.kt
│ │ │ │ ├── GetGamesByDeckCodeAndNameUseCase.kt
│ │ │ │ ├── SummaryFragment.kt
│ │ │ │ └── SummaryViewModel.kt
│ │ │ ├── diagnose/
│ │ │ │ └── DiagnoseActivity.kt
│ │ │ ├── filter/
│ │ │ │ └── FilterActivity.kt
│ │ │ ├── main/
│ │ │ │ ├── CardListFragment.kt
│ │ │ │ ├── DeckCardListFragment.kt
│ │ │ │ ├── GraveyardFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainViewModel.kt
│ │ │ │ ├── OpponentGraveyardFragment.kt
│ │ │ │ ├── OpponentHandCardsFragment.kt
│ │ │ │ └── UserGraveyardFragment.kt
│ │ │ ├── migrate/
│ │ │ │ ├── MigrateDataConvert.kt
│ │ │ │ ├── MigrateMainActivity.kt
│ │ │ │ ├── SocketClientActivity.kt
│ │ │ │ └── SocketServerActivity.kt
│ │ │ ├── permissions/
│ │ │ │ ├── PermissionsActivity.kt
│ │ │ │ └── PermissionsViewModel.kt
│ │ │ ├── records/
│ │ │ │ ├── RecordAdapter.kt
│ │ │ │ ├── RecordsActivity.kt
│ │ │ │ └── RecordsViewModel.kt
│ │ │ ├── settings/
│ │ │ │ ├── SettingsActivity.kt
│ │ │ │ └── SettingsViewModel.kt
│ │ │ ├── splash/
│ │ │ │ ├── SplashActivity.kt
│ │ │ │ └── SplashViewModel.kt
│ │ │ ├── summary/
│ │ │ │ ├── BattleRateItem.kt
│ │ │ │ ├── BattleRateItemAdapter.kt
│ │ │ │ ├── BattleRateListFragment.kt
│ │ │ │ ├── BattleRateListViewModel.kt
│ │ │ │ ├── DeckBattleRateListViewModel.kt
│ │ │ │ ├── GetDeckBattleRateListUseCase.kt
│ │ │ │ ├── GetHeroBattleRateListUseCase.kt
│ │ │ │ ├── HeroBattleRateListViewModel.kt
│ │ │ │ ├── RateByDeckFragment.kt
│ │ │ │ ├── RateByHeroFragment.kt
│ │ │ │ └── SummaryActivity.kt
│ │ │ ├── support/
│ │ │ │ └── SupportActivity.kt
│ │ │ ├── sync/
│ │ │ │ ├── SyncCardDataActivity.kt
│ │ │ │ └── SyncCardDataViewModel.kt
│ │ │ ├── test/
│ │ │ │ ├── CreateRecordActivity.kt
│ │ │ │ ├── LocalFileParserActivity.kt
│ │ │ │ └── TestActivity.kt
│ │ │ ├── theme/
│ │ │ │ └── ThemeActivity.kt
│ │ │ ├── writeconfig/
│ │ │ │ └── WriteConfigActivity.kt
│ │ │ ├── zonecards/
│ │ │ │ └── ZoneCardsActivity.kt
│ │ │ └── zoneevents/
│ │ │ ├── ListModeFragment.kt
│ │ │ ├── ZoneEventsActivity.kt
│ │ │ └── ZoneEventsViewModel.kt
│ │ └── res/
│ │ ├── color/
│ │ │ └── module_game_state.xml
│ │ ├── drawable/
│ │ │ ├── module_baseline_arrow_back_white_24dp.xml
│ │ │ ├── module_baseline_clear_black_24dp.xml
│ │ │ ├── module_baseline_clear_red_500_24dp.xml
│ │ │ ├── module_baseline_done_black_24dp.xml
│ │ │ ├── module_baseline_done_green_500_24dp.xml
│ │ │ ├── module_baseline_done_white_24dp.xml
│ │ │ ├── module_baseline_drag_handle_white_24dp.xml
│ │ │ ├── module_baseline_keyboard_arrow_right_grey_500_24dp.xml
│ │ │ ├── module_baseline_pie_chart_white_24dp.xml
│ │ │ ├── module_baseline_play_arrow_white_24dp.xml
│ │ │ ├── module_baseline_settings_white_24dp.xml
│ │ │ ├── module_baseline_sync_white_24dp.xml
│ │ │ └── module_baseline_zoom_out_map_white_24dp.xml
│ │ ├── drawable-xxxhdpi/
│ │ │ └── module_bg_splash.xml
│ │ ├── layout/
│ │ │ ├── module_activity_class_battle_detail.xml
│ │ │ ├── module_activity_create_record.xml
│ │ │ ├── module_activity_deck_battle_detail.xml
│ │ │ ├── module_activity_deck_code_parser.xml
│ │ │ ├── module_activity_diagnose.xml
│ │ │ ├── module_activity_filter.xml
│ │ │ ├── module_activity_local_file_parser.xml
│ │ │ ├── module_activity_main.xml
│ │ │ ├── module_activity_migrate_main.xml
│ │ │ ├── module_activity_permissions.xml
│ │ │ ├── module_activity_records.xml
│ │ │ ├── module_activity_settings.xml
│ │ │ ├── module_activity_socket_client.xml
│ │ │ ├── module_activity_socket_server.xml
│ │ │ ├── module_activity_summary.xml
│ │ │ ├── module_activity_summary_chart.xml
│ │ │ ├── module_activity_support.xml
│ │ │ ├── module_activity_sync_card_data.xml
│ │ │ ├── module_activity_test.xml
│ │ │ ├── module_activity_theme.xml
│ │ │ ├── module_activity_write_config.xml
│ │ │ ├── module_activity_zone_cards.xml
│ │ │ ├── module_activity_zone_events.xml
│ │ │ ├── module_dialog_card_preview.xml
│ │ │ ├── module_floating_window.xml
│ │ │ ├── module_fragment_card_list.xml
│ │ │ ├── module_fragment_deck_battle_detail_deck_detail.xml
│ │ │ ├── module_fragment_deck_battle_detail_summary.xml
│ │ │ ├── module_fragment_graveyard.xml
│ │ │ ├── module_fragment_list_mode.xml
│ │ │ ├── module_fragment_loading.xml
│ │ │ ├── module_fragment_opponent_hand_cards.xml
│ │ │ ├── module_header_summary.xml
│ │ │ ├── module_item_card.xml
│ │ │ ├── module_item_chip_filter.xml
│ │ │ ├── module_item_class_battle_detail.xml
│ │ │ ├── module_item_footer_with_fab.xml
│ │ │ ├── module_item_opponent_hand_card.xml
│ │ │ ├── module_item_record.xml
│ │ │ ├── module_item_summary_battle.xml
│ │ │ ├── module_item_text.xml
│ │ │ ├── module_item_zone_card.xml
│ │ │ └── module_item_zone_event_list_mode.xml
│ │ ├── values/
│ │ │ ├── arrays.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ ├── values-night/
│ │ │ └── themes.xml
│ │ └── values-zh-rCN/
│ │ └── strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs_tracker/
│ └── module/
│ └── ExampleUnitTest.kt
├── settings.gradle
├── shared/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs/
│ └── shared/
│ └── entity/
│ └── CardType.kt
├── simulator/
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── ke/
│ │ └── hs/
│ │ └── simulator/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── ke/
│ │ └── hs/
│ │ └── simulator/
│ │ └── cards/
│ │ └── base/
│ │ ├── HeroCard.kt
│ │ ├── ICard.kt
│ │ ├── MinionCard.kt
│ │ ├── SpellCard.kt
│ │ └── WeaponCard.kt
│ └── test/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs/
│ └── simulator/
│ └── ExampleUnitTest.kt
└── writer/
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── release/
│ ├── output-metadata.json
│ └── writer-release.apk
└── src/
├── androidTest/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs/
│ └── writer/
│ └── ExampleInstrumentedTest.kt
├── main/
│ ├── AndroidManifest.xml
│ └── res/
│ ├── drawable/
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── values-night/
│ └── themes.xml
└── test/
└── java/
└── com/
└── ke/
└── hs/
└── writer/
└── ExampleUnitTest.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 keluokeda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# hs_tracker
## Android14 use this https://github.com/keluokeda/Hs
An automatic Hearthstone tracker for Android
#### 炉石传说记牌器,支持Android12、Android13


### QQ群:825215274
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
id 'kotlin-parcelize'
}
kapt {
correctErrorTypes true
}
android {
compileSdk libs.versions.compilesdk.get().toInteger()
defaultConfig {
applicationId "com.ke.hs_tracker.app"
minSdk libs.versions.minsdk.get().toInteger()
targetSdk libs.versions.targetsdk.get().toInteger()
versionCode 31
versionName "1.3.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding = true
}
signingConfigs {
myConfig {
storeFile file(RELEASE_FILE)
storePassword RELEASE_storePassword
keyAlias RELEASE_keyAlias
keyPassword RELEASE_keyPassword
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
zipAlignEnabled true
signingConfig signingConfigs.myConfig
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//signingConfig signingConfigs.config
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
namespace 'com.ke.hs_tracker.app'
}
dependencies {
implementation project(path: ':module')
implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.constraint.layout)
implementation(libs.fragment.ktx)
implementation(libs.activity)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.support.v4)
implementation(libs.logger)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
implementation(libs.ke.mvvm)
implementation(libs.moshi)
kapt(libs.moshi.codegen)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/release/output-metadata.json
================================================
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.ke.hs_tracker.app",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 31,
"versionName": "1.3.1",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:name=".App" />
</manifest>
================================================
FILE: app/src/main/java/com/ke/hs_tracker/app/App.kt
================================================
package com.ke.hs_tracker.app
import com.ke.hs_tracker.module.MainApplication
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : MainApplication()
================================================
FILE: app/src/main/java/com/ke/hs_tracker/app/MainActivity.kt
================================================
package com.ke.hs_tracker.app
//
//import android.os.Bundle
//import androidx.appcompat.app.AppCompatActivity
//import androidx.documentfile.provider.DocumentFile
//import androidx.lifecycle.lifecycleScope
//import com.ke.hs_tracker.app.databinding.ActivityMainBinding
//import com.ke.hs_tracker.module.findHSDataFilesDir
//import com.ke.hs_tracker.module.log
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.launch
//import kotlinx.coroutines.withContext
//
//class MainActivity : AppCompatActivity() {
//
//
// private lateinit var powerLogFile: DocumentFile
//
// private lateinit var binding: ActivityMainBinding
//
//// private lateinit var inputStream: InputStream
//
// private var oldLogSize = 0L
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// binding = ActivityMainBinding.inflate(layoutInflater)
// setContentView(binding.root)
//
//
//
// binding.init.setOnClickListener {
// lifecycleScope.launch {
//
// oldLogSize = 0
// val logsDir = findHSDataFilesDir("Logs")
//
// val file = logsDir?.findFile("Power.log")
// if (file != null) {
// "初始化成功 $file".log()
// powerLogFile = file
// } else {
// "初始化失败".log()
// }
//
//
// }
// }
//
// binding.delete.setOnClickListener {
// try {
// "删除本地日志结果 ${deleteFile("power.log")}".log()
//
// val result = powerLogFile.delete()
// "删除文件结果 $result".log()
// } catch (e: Exception) {
//// binding.content.text = e.message
// "删除文件失败".log()
// e.printStackTrace()
// }
// }
//
// binding.load.setOnClickListener {
//
// lifecycleScope.launch {
// try {
//
//
// contentResolver.openInputStream(powerLogFile.uri)!!.reader()
// .apply {
// if (oldLogSize > 0) {
// val skip = skip(oldLogSize)
// "跳过的字节数量 $skip".log()
// }
// binding.content.text = readText().also {
// oldLogSize += it.length
// writeToLocal(it)
// }
//
// close()
// }
//
// } catch (e: Exception) {
// binding.content.text = e.message
// }
// }
//
//
// }
// }
//
// private suspend fun writeToLocal(content: String) {
// withContext(Dispatchers.IO) {
// openFileOutput("power.log", MODE_APPEND)
// .writer().apply {
// append(content)
// flush()
// close()
// }
// }
// }
//
//
//// private suspend fun getPowerLogFileSize(): Long {
//// return withContext(Dispatchers.IO) {
//// try {
//// contentResolver.query(powerLogFile.uri, arrayOf(OpenableColumns.SIZE), null, null)
//// ?.apply {
//// moveToFirst()
//// return@withContext getLong(0).also {
//// close()
//// }
////
//// }
//// } catch (e: Exception) {
//// e.printStackTrace()
//// return@withContext 0
//// }
////
////
////
//// 0
//// }
//// }
//}
================================================
FILE: build.gradle
================================================
plugins {
id 'com.android.application' version '8.0.2' apply false
id 'com.android.library' version '8.0.2' apply false
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
id 'org.jetbrains.kotlin.jvm' version '1.8.0' apply false
id 'com.google.dagger.hilt.android' version '2.45' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: core/.gitignore
================================================
/build
================================================
FILE: core/build.gradle
================================================
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
id 'kotlin-kapt'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation(libs.retrofit)
implementation(libs.retrofit.converter.moshi)
implementation(libs.moshi)
kapt(libs.moshi.codegen)
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
// implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/api/HearthStoneJsonApi.kt
================================================
package com.ke.hs_tracker.core.api
import com.ke.hs_tracker.core.entity.Card
import retrofit2.http.GET
import retrofit2.http.Path
interface HearthStoneJsonApi {
/**
* 获取卡牌数据
*/
@GET("v1/{versionCode}/{region}/cards.json")
suspend fun getCardJsonList(
@Path("versionCode") versionCode: String,
@Path("region") region: String,
): List<Card>
companion object {
const val BASE_URL = "https://api.hearthstonejson.com/"
}
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/BlockType.kt
================================================
package com.ke.hs_tracker.core.entity
enum class BlockType {
/**
* 攻击
*/
Attack,
/**
* 死亡
*/
Deaths,
/**
* 触发
*/
Trigger,
/**
* 打出一张卡牌
*/
Play,
/**
* 卡牌生效
*/
Power,
/**
* 交易
*/
Trade
}
internal fun String.toBlockType(fallback: BlockType = BlockType.Trigger): BlockType {
BlockType.values().forEach {
if (it.name.equals(this, true)) {
return it
}
}
return fallback
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/Card.kt
================================================
package com.ke.hs_tracker.core.entity
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonClass
import com.squareup.moshi.ToJson
@JsonClass(generateAdapter = true)
data class Card(
val name: String,
val cost: Int = 0,
val id: String,
val dbfId: Int,
val text: String = "",
//属于哪个版本 例如 TGT
val set: String,
val type: CardType = CardType.None,
val cardClass: CardClass,
val classes: List<CardClass> = emptyList(),
val flavor: String,
val attach: Int = 0,
val health: Int = 0
)
enum class CardType {
/**
* 英雄
*/
Hero,
/**
* 英雄技能
*/
HeroPower,
/**
*衍生牌
*/
Enchantment,
/**
* 法术
*/
Spell,
/**
* 随从
*/
Minion,
/**
* 武器
*/
Weapon,
None
}
class CardTypeAdapter {
@FromJson
fun fromJson(value: String): CardType {
return CardType.values()
.find { it.name.equals(value.replace("_", ""), true) } ?: CardType.None
}
@ToJson
fun toJson(cardType: CardType) = cardType.name.uppercase()
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/CardClass.kt
================================================
package com.ke.hs_tracker.core.entity
enum class CardClass {
/**
* 法师
*/
Mage,
/**
* 术士
*/
Warlock,
/**
* 牧师
*/
Priest,
/**
* 德鲁伊
*/
Druid,
Neutral
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/CurrentDeck.kt
================================================
package com.ke.hs_tracker.core.entity
data class CurrentDeck(
val name: String,
val code: String
)
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/Entity.kt
================================================
package com.ke.hs_tracker.core.entity
import com.ke.hs_tracker.core.parser.PowerParserImpl
//有三种形式
//1,Entity=[entityName=腐食研习 id=29 zone=PLAY zonePos=0 cardId=SCH_300 player=1]
//2,Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=29 zone=HAND zonePos=3 cardId= player=1]
//3,Entity=失落的裤子#5629
data class Entity(
val entityName: String,
val gameCardType: GameCardType? = null,
val id: Int = -1,
val zone: Zone = Zone.Play,
val zonePosition: Int = -1,
val cardId: String? = null,
val player: Int = -1
) {
val entityType: EntityType
get() = when {
gameCardType == null && id == -1 && zone == Zone.Play && zonePosition == -1 && cardId == null && player == -1 -> EntityType.Name
gameCardType == GameCardType.Invalid -> EntityType.Invalid
else -> EntityType.Clear
}
companion object {
internal fun createFromContent(content: String): Entity? {
if (content == "0") {
return null
}
var matchResult = PowerParserImpl.FULL_ENTITY_CONTENT1_PATTERN.matchEntire(content)
if (matchResult != null) {
return Entity(
matchResult.groupValues[1],
matchResult.groupValues[2].toCardType(GameCardType.Invalid),
matchResult.groupValues[3].toIntOrNull() ?: 0,
matchResult.groupValues[4].toZone(),
matchResult.groupValues[5].toIntOrNull() ?: 0,
matchResult.groupValues[6].ifBlank { null },
matchResult.groupValues[7].toIntOrNull() ?: 0
)
}
matchResult = PowerParserImpl.FULL_ENTITY_CONTENT2_PATTERN.matchEntire(content)
?: return Entity(content)
return Entity(
matchResult.groupValues[1],
GameCardType.Invalid,
matchResult.groupValues[2].toIntOrNull() ?: 0,
matchResult.groupValues[3].toZone(),
matchResult.groupValues[4].toIntOrNull() ?: 0,
matchResult.groupValues[5].ifBlank { null },
matchResult.groupValues[6].toIntOrNull() ?: 0
)
}
}
}
enum class EntityType {
//Entity=失落的裤子#5629
Name,
//Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=29 zone=HAND zonePos=3 cardId= player=1]
Invalid,
//Entity=[entityName=腐食研习 id=29 zone=PLAY zonePos=0 cardId=SCH_300 player=1]
Clear
}
data class GameEntity(
val gameCardType: GameCardType,
val entityId: Int
)
/**
* 玩家
*/
data class Player(
val entityId: Int,
val playerId: Int,
val controller: Int,
val gameCardType: GameCardType,
val heroEntity: Int,
/**
* 手牌上限
*/
val maxHandSize: Int,
/**
* 起始手牌
*/
val startHandSize: Int,
val teamId: Int,
/**
* 费用上限 一般为10
*/
val maxResources: Int
) {
companion object {
internal fun fromMap(map: Map<String, String>): Player {
return Player(
entityId = map["entityid"]?.toIntOrNull() ?: 0,
playerId = map["playerid"]?.toIntOrNull() ?: 0,
controller = map["controller"]?.toIntOrNull() ?: 0,
gameCardType = (map["cardtype"] ?: "").toCardType(),
heroEntity = map["heroentity"]?.toIntOrNull() ?: 0,
maxHandSize = map["maxhandsize"]?.toIntOrNull() ?: 0,
startHandSize = map["starthandsize"]?.toIntOrNull() ?: 0,
teamId = map["teamid"]?.toIntOrNull() ?: 0,
maxResources = map["maxresources"]?.toIntOrNull() ?: 0
)
}
}
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/GameCardType.kt
================================================
package com.ke.hs_tracker.core.entity
/**
* 卡牌类型
*/
enum class GameCardType {
Game,
/**
* 玩家
*/
Player,
/**
* 英雄
*/
Hero,
/**
* 英雄技能
*/
HeroPower,
/**
* 牌库中的牌的状态
*/
Invalid,
/**
* 随从身上的buff或战场上的buff(例如下一张法强怪法力值减少1)
*/
Enchantment,
/**
* 法术
*/
Spell,
/**
* 随从
*/
Minion
}
/**
* 字符串转 CardType类型
*/
internal fun String.toCardType(fallback: GameCardType = GameCardType.Game): GameCardType {
return GameCardType.values().find {
it.name.equals(this.replace("_", ""), true)
} ?: fallback
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/InsertStackResult.kt
================================================
package com.ke.hs_tracker.core.entity
sealed interface InsertStackResult {
/**
* 插入成功
*/
object Success : InsertStackResult
/**
* 不能插入,例如不在Block内的TAG_CHANGE
*/
object CanNotInsert : InsertStackResult
/**
* 结束了
* @param powerTag tag
* @param handled 是否处理了本次log日志,例如 FULL_ENTITY - Updating 跟着一个 FULL_ENTITY - Updating的情况,handled就是true,表示已经处理了,不需要在进行处理;如果是FULL_ENTITY - Updating
* 跟着一个 TAG_CHANGE,就表示没有处理,需要调用这个方法的自行处理log数据
*/
data class Over(val powerTag: PowerTag, val handled: Boolean) : InsertStackResult
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/LogType.kt
================================================
package com.ke.hs_tracker.core.entity
enum class LogType(val replace: String) {
PowerTaskList("PowerTaskList.DebugPrintPower() -"),
GameState("GameState.DebugPrintGame() -")
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/Mechanics.kt
================================================
package com.ke.hs_tracker.core.entity
/**
* 类型
*/
enum class Mechanics {
/**
* 过载
*/
Overload,
/**
* 战吼
*/
Battlecry,
/**
* 嘲讽
*/
Taunt,
/**
* 突袭
*/
Rush,
/**
* 潜行
*/
Stealth,
/**
* 圣盾
*/
DivineShield,
/**
* 法强
*/
SpellPower,
/**
* 亡语
*/
Deathrattle,
/**
* 荣誉击杀
*/
hHonorableKill,
/**
* 法力迸发
*/
SpellBurst,
/**
* 腐蚀
*/
Corrupt,
/**
* 暴怒
*/
Frenzy,
/**
* 发现
*/
Discover,
/**
* 冻结
*/
Freeze,
/**
* 连击
*/
Combo,
/**
* 无法攻击
*/
CantAttack,
/**
* 触发
*/
TriggerVisual,
/**
* 奥秘
*/
Secret,
/**
* 交易
*/
Tradeable,
/**
* 激励
*/
Inspire,
/**
* 沉默
*/
Silence,
/**
* 风怒
*/
Windfury,
/**
* 回响
*/
Echo
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/NestedTag.kt
================================================
package com.ke.hs_tracker.core.entity
internal sealed interface NestedTag {
object CreateGame : NestedTag
data class GameEntity(val id: Int) : NestedTag
data class Tag(val key: String, val value: String) : NestedTag {
fun toPair(): Pair<String, String> = key to value
}
data class Player(val entityId: Int, val playerId: Int) : NestedTag
//FULL_ENTITY - Updating [entityName=UNKNOWN ENTITY [cardType=INVALID] id=63 zone=DECK zonePos=0 cardId= player=2] CardID=
//FULL_ENTITY - Updating [entityName=全副武装! id=65 zone=PLAY zonePos=0 cardId=HERO_01bp player=1] CardID=HERO_01bp
data class FullEntity(
val entity: Entity
) : NestedTag
data class Block(
val blockType: BlockType,
val entity: Entity,
val target: Entity?
) : NestedTag
data class TagChange(
val entity: Entity,
val tag: String,
val value: String
) : NestedTag {
fun convert(): PowerTag.PowerTaskList.TagChange {
return PowerTag.PowerTaskList.TagChange(entity, tag, value)
}
}
data class ShowEntity(
val entity: Entity,
val cardId: String
) : NestedTag
object BlockEnd : NestedTag
}
//internal fun NestedTag.FullEntity.toUpdating(): PowerTag.PowerTaskList.FullEntity.Updating {
// return PowerTag.PowerTaskList.FullEntity.Updating(
// entityName, cardType, id, zone, zonePosition, cardId, player
// )
//}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/PowerTag.kt
================================================
package com.ke.hs_tracker.core.entity
sealed interface PowerTag {
sealed interface PowerTaskList : PowerTag {
/**
* 创建游戏
*/
//D 19:55:18.1257030 GameState.DebugPrintPower() - CREATE_GAME
//D 19:55:18.1257030 GameState.DebugPrintPower() - GameEntity EntityID=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CARDTYPE value=GAME
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ZONE value=PLAY
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ENTITY_ID value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=937 value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=SPAWN_TIME_COUNT value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=GAME_SEED value=1950487951
//D 19:55:18.1257030 GameState.DebugPrintPower() - Player EntityID=2 PlayerID=1 GameAccountId=[hi=144115211015832391 lo=191215280]
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CONTROLLER value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CARDTYPE value=PLAYER
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=PLAYER_ID value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=HERO_ENTITY value=64
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXHANDSIZE value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=STARTHANDSIZE value=4
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=TEAM_ID value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ZONE value=PLAY
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ENTITY_ID value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXRESOURCES value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=SPAWN_TIME_COUNT value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=AVRANK value=336
//D 19:55:18.1257030 GameState.DebugPrintPower() - Player EntityID=3 PlayerID=2 GameAccountId=[hi=144115211015832391 lo=44511141]
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CONTROLLER value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CARDTYPE value=PLAYER
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=PLAYER_ID value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=HERO_ENTITY value=66
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXHANDSIZE value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=STARTHANDSIZE value=4
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=TEAM_ID value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ZONE value=PLAY
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ENTITY_ID value=3
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXRESOURCES value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=SPAWN_TIME_COUNT value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=AVRANK value=338
data class CreateGame(
val gameEntity: GameEntity,
val player1: Player,
val player2: Player
) : PowerTaskList
data class TagChange(
val entity: Entity,
val tag: String,
val value: String,
) : PowerTaskList {
/**
* 是否是游戏完成的标志
*/
val isGameComplete: Boolean =
entity.entityName == "GameEntity" && tag.equals("state", true) && value.equals(
"COMPLETE",
true
)
}
data class FullEntity(
val entity: Entity,
val payloads: MutableMap<String, String> = mutableMapOf()
) : PowerTaskList {
fun append(value: Pair<String, String>) {
payloads[value.first] = value.second
}
}
data class ShowEntity(
val entity: Entity,
val cardId: String,
val payloads: MutableMap<String, String> = mutableMapOf()
) : PowerTaskList {
fun append(value: Pair<String, String>) {
payloads[value.first] = value.second
}
}
data class Block(
val type: BlockType,
val entity: Entity,
val target: Entity?,
val list: List<PowerTag>
) : PowerTaskList
}
sealed interface GameState : PowerTag {
data class BuildNumber(val number: String) : GameState
data class GameType(val type: String) : GameState
data class FormatType(val type: String) : GameState
data class ScenarioID(val id: String) : GameState
data class PlayerMapping(val id: Int, val name: String) : GameState {
/**
* 是否是先手
*/
val first: Boolean = id == 1
/**
* 是否是当前用户
*/
val isUser: Boolean = name != "UNKNOWN HUMAN PLAYER"
}
}
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/entity/Zone.kt
================================================
package com.ke.hs_tracker.core.entity
enum class Zone {
/**
* 战场
*/
Play,
/**
* 牌库
*/
Deck,
/**
* 发现的牌的位置
*/
SetAside,
/**
* 墓地 打出的法术牌和死亡的随从会进入
*/
Graveyard,
/**
* 手牌
*/
Hand
}
internal fun String.toZone(fallback: Zone = Zone.Deck): Zone {
return Zone.values().firstOrNull {
it.name.equals(this, true)
} ?: fallback
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/extensions.kt
================================================
package com.ke.hs_tracker.core
import com.ke.hs_tracker.core.parser.PowerParserImpl
import java.util.*
fun String.removeTime(): Triple<String, Date, String> {
val content = substring(PowerParserImpl.TIME_PREFIX_SIZE)
val start = substring(0, 1)
val hms = substring(2, 10).split(":")
val calendar = Calendar.getInstance()
calendar.set(
Calendar.HOUR_OF_DAY, hms[0].toInt()
)
calendar.set(
Calendar.MINUTE, hms[1].toInt()
)
calendar.set(
Calendar.SECOND, hms[2].toInt()
)
return Triple(
start,
calendar.time,
content
)
}
fun main(){
val text = "I 22:23:35.6401730 AAECAaoIBJzOA6beA8L2A9ySBA3buAPhzAPNzgPw1AOK5APq5wP67APk9gOF+gOogQSVkgT5nwT6nwQA"
println(text.removeTime().toString())
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/parser/BlockTagStack.kt
================================================
package com.ke.hs_tracker.core.parser
import com.ke.hs_tracker.core.entity.*
import java.lang.Exception
import java.util.*
import kotlin.text.lowercase
import kotlin.text.replace
import kotlin.text.toInt
interface BlockTagStack {
/**
* 插入一条日志
*/
fun insert(line: String): InsertStackResult
}
internal class BlockTagStackImpl : BlockTagStack {
private val nestedTagList = LinkedList<NestedTag>()
override fun insert(line: String): InsertStackResult {
//CREATE_GAME
var matchResult = PowerParserImpl.CREATE_GAME_PATTERN.matchEntire(line)
if (matchResult != null) {
//游戏开始
nestedTagList.clear()
nestedTagList.add(NestedTag.CreateGame)
return InsertStackResult.Success
}
//GameEntity EntityID=1
matchResult = PowerParserImpl.GAME_ENTITY_PATTERN.matchEntire(line)
if (matchResult != null) {
val entityId = matchResult.groupValues[1].toInt()
nestedTagList.add(NestedTag.GameEntity(entityId))
return InsertStackResult.Success
}
//Player EntityID=2 PlayerID=1 GameAccountId=[hi=144115211015832391 lo=191215280]
matchResult = PowerParserImpl.PLAYER_ENTITY_PATTERN.matchEntire(line)
if (matchResult != null) {
val entityId = matchResult.groupValues[1].toInt()
val playerId = matchResult.groupValues[2].toInt()
nestedTagList.add(NestedTag.Player(entityId, playerId))
return InsertStackResult.Success
}
//tag=CARDTYPE value=GAME
matchResult = PowerParserImpl.TAG_PATTERN.matchEntire(line)
if (matchResult != null) {
val key = matchResult.groupValues[1]
val value = matchResult.groupValues[2]
nestedTagList.add(NestedTag.Tag(key, value))
return InsertStackResult.Success
}
//BLOCK_START
// BlockType=TRIGGER
// Entity=GameEntity
// EffectCardId=System.Collections.Generic.List`1[System.String]
// EffectIndex=-1
// Target=0
// SubOption=-1
// TriggerKeyword=TAG_NOT_SET
matchResult = PowerParserImpl.BLOCK_START_PATTERN.matchEntire(line)
if (matchResult != null) {
val blockType = matchResult.groupValues[1].toBlockType()
val entity = Entity.createFromContent(matchResult.groupValues[2])!!
val target = Entity.createFromContent(matchResult.groupValues[5])
val block = NestedTag.Block(blockType, entity, target)
nestedTagList.add(block)
return InsertStackResult.Success
}
matchResult = PowerParserImpl.BLOCK_END_PATTERN.matchEntire(line)
if (matchResult != null) {
//块结束了
nestedTagList.add(NestedTag.BlockEnd)
// val blockStartList = nestedTagList.filterIsInstance<NestedTag.Block>()
val blockCount = nestedTagList.count {
it is NestedTag.Block
}
val blockEndCount = nestedTagList.count { it is NestedTag.BlockEnd }
if (blockCount == blockEndCount) {
return InsertStackResult.Over(flushBlock(nestedTagList), true)
} else {
return InsertStackResult.Success
}
}
//TAG_CHANGE Entity=阿克萌德#51240 tag=CURRENT_PLAYER value=1
//TAG_CHANGE Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=29 zone=DECK zonePos=0 cardId= player=1] tag=ZONE_POSITION value=1
matchResult = PowerParserImpl.TAG_CHANGE_PATTERN.matchEntire(line)
if (matchResult != null) {
return when (val first = nestedTagList.firstOrNull()) {
is NestedTag.FullEntity -> {
val powerTag = flushFullEntityWhenFirst()
//处理堆栈
InsertStackResult.Over(powerTag, false)
}
is NestedTag.ShowEntity -> {
val showEntity = PowerTag.PowerTaskList.ShowEntity(
first.entity,
first.cardId
)
nestedTagList.forEach {
if (it is NestedTag.TagChange) {
showEntity.payloads[it.tag] = it.value
}
}
nestedTagList.clear()
InsertStackResult.Over(showEntity, false)
}
is NestedTag.Block -> {
val tagChange = NestedTag.TagChange(
Entity.createFromContent(matchResult.groupValues[1])!!,
matchResult.groupValues[2],
matchResult.groupValues[3],
)
nestedTagList.add(tagChange)
InsertStackResult.Success
}
else -> {
InsertStackResult.CanNotInsert
}
}
}
//FULL_ENTITY - Updating [entityName=UNKNOWN ENTITY [cardType=INVALID] id=4 zone=DECK zonePos=0 cardId= player=1] CardID=
matchResult = PowerParserImpl.FULL_ENTITY_PATTERN.matchEntire(line)
if (matchResult != null) {
val first = nestedTagList.firstOrNull()
if (first is NestedTag.CreateGame) {
//create game 接 full entity
val createGame = createCreateGameTag()
val content = matchResult.groupValues[1]
insertFullEntity(content)
return InsertStackResult.Over(createGame, true)
} else if (first is NestedTag.FullEntity) {
//连续两个full entity
val result = flushFullEntityWhenFirst()
insertFullEntity(matchResult.groupValues[1])
return InsertStackResult.Over(result, true)
}
insertFullEntity(matchResult.groupValues[1])
return InsertStackResult.Success
}
//SHOW_ENTITY - Updating Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=85 zone=SETASIDE zonePos=0 cardId= player=2] CardID=SCH_231e
matchResult = PowerParserImpl.SHOW_ENTITY.matchEntire(line)
if (matchResult != null) {
val first = nestedTagList.firstOrNull()
val fullEntity = if (first is NestedTag.FullEntity) {
flushFullEntityWhenFirst()
} else null
val entity = Entity.createFromContent(matchResult.groupValues[1])!!
val cardId = matchResult.groupValues[2]
nestedTagList.add(NestedTag.ShowEntity(entity, cardId))
return if (fullEntity == null) {
InsertStackResult.Success
} else {
InsertStackResult.Over(fullEntity, true)
}
}
return InsertStackResult.CanNotInsert
}
/**
* 如果栈中的第一个是FullEntity,就开始处理
*/
private fun flushFullEntityWhenFirst(): PowerTag.PowerTaskList.FullEntity {
val map = mutableMapOf<String, String>()
val first = nestedTagList.removeFirst() as NestedTag.FullEntity
nestedTagList.map {
it as NestedTag.Tag
}.forEach {
map[it.key] = it.value
}
//清空栈
nestedTagList.clear()
return PowerTag.PowerTaskList.FullEntity(
first.entity,
map
)
// return when {
// isInsertCardToDeck(first) -> {
// //插入一张卡牌到牌库
// flushFullEntityInsertCardToDeck()
// }
// isInsertHeroToPlay(first) -> {
// //放置英雄牌到战场
// flushFullEntityInsertHeroToPlay()
// }
// isInsertHeroPowerToPlay(first) -> {
// //放置英雄技能到战场
// flushFullEntityInsertHeroPowerToPlay()
// }
// else -> throw RuntimeException("无法处理的 full entity $first")
// }
}
// /**
// * 是否是置入英雄技能到战场
// */
// private fun isInsertHeroPowerToPlay(fullEntity: NestedTag.FullEntity): Boolean {
// if (fullEntity.entity.zone != Zone.Play) {
// return false
// }
//
// nestedTagList.forEach {
// if (it is NestedTag.Tag && "cardType".equals(
// it.key,
// true
// ) && GameCardType.HeroPower.name.equals(
// it.value.replace("_", ""),
// true
// )
// ) {
// return true
// }
// }
//
//
// return false
// }
// private fun flushFullEntityInsertHeroPowerToPlay(): PowerTag.PowerTaskList.FullEntity.InsertHeroPowerToPlay {
// var last = nestedTagList.removeLastOrNull()
// //移除第一个
// val entity = (nestedTagList.removeFirst() as NestedTag.FullEntity).entity
// val map = mutableMapOf<String, String>()
// while (last != null) {
// when (last) {
// is NestedTag.Tag -> {
// map[last.key] = last.value
// }
// else -> {
// throw RuntimeException("last的类型必须是Tag 但现在是 $last")
// }
// }
// last = nestedTagList.removeLastOrNull()
// }
// return PowerTag.PowerTaskList.FullEntity.InsertHeroPowerToPlay.createFromEntityAndMap(
// entity,
// map
// )
// }
//
// private fun flushFullEntityInsertHeroToPlay(): PowerTag.PowerTaskList.FullEntity.InsertHeroToPlay {
// var last = nestedTagList.removeLastOrNull()
// //移除第一个
// val entity = (nestedTagList.removeFirst() as NestedTag.FullEntity).entity
// val map = mutableMapOf<String, String>()
// while (last != null) {
// when (last) {
// is NestedTag.Tag -> {
// map[last.key] = last.value
// }
// else -> {
// throw RuntimeException("last的类型必须是Tag 但现在是 $last")
// }
// }
// last = nestedTagList.removeLastOrNull()
// }
// return PowerTag.PowerTaskList.FullEntity.InsertHeroToPlay.createFromEntityAndMap(
// entity,
// map
// )
// }
// /**
// * 是否是置入英雄卡到战场
// */
// private fun isInsertHeroToPlay(fullEntity: NestedTag.FullEntity): Boolean {
// if (fullEntity.entity.zone != Zone.Play) {
// return false
// }
//
// nestedTagList.forEach {
// if (it is NestedTag.Tag && "cardType".equals(
// it.key,
// true
// ) && GameCardType.Hero.name.equals(
// it.value,
// true
// )
// ) {
// return true
// }
// }
//
//
// return false
// }
// /**
// * 是否是置入英雄卡到战场
// */
// private fun isInsertCardToDeck(fullEntity: NestedTag.FullEntity): Boolean {
//
// return fullEntity.entity.zone == Zone.Deck && fullEntity.entity.gameCardType == GameCardType.Invalid
// }
// private fun flushFullEntityInsertCardToDeck(): PowerTag.PowerTaskList.FullEntity.InsertToDeck {
// var last = nestedTagList.removeLastOrNull()
// //移除第一个
// val fullEntity = nestedTagList.removeFirst() as NestedTag.FullEntity
// val map = mutableMapOf<String, String>()
// while (last != null) {
// when (last) {
// is NestedTag.Tag -> {
// map[last.key] = last.value
// }
// else -> {
// throw RuntimeException("last的类型必须是Tag 但现在是 $last")
// }
// }
// last = nestedTagList.removeLastOrNull()
// }
//
// if (map.size != 3) throw RuntimeException("在插入卡牌到牌库的情况下,tag数量必须是3个")
//
// return PowerTag.PowerTaskList.FullEntity.InsertToDeck.createFromEntityAndMap(
// fullEntity.entity,
// map
// )
//
// }
private fun insertFullEntity(content: String) {
val fullEntity = createFullEntityByContent(content)
nestedTagList.add(fullEntity)
}
/**
* 根据字符串创建FullEntity
*/
//[entityName=UNKNOWN ENTITY [cardType=INVALID] id=4 zone=DECK zonePos=0 cardId= player=1]
//[entityName=加尔鲁什·地狱咆哮 id=64 zone=PLAY zonePos=0 cardId=HERO_01 player=1]
private fun createFullEntityByContent(content: String): NestedTag.FullEntity {
val entity: Entity = Entity.createFromContent(content)!!
return NestedTag.FullEntity(entity)
}
private fun createCreateGameTag(): PowerTag.PowerTaskList.CreateGame {
val first = nestedTagList.removeFirstOrNull()
if (first == NestedTag.CreateGame) {
val keyValueMap = mutableMapOf<String, String>()
var last = nestedTagList.removeLastOrNull()
var player1: Player? = null
var player2: Player? = null
while (last != null) {
when (last) {
is NestedTag.GameEntity -> {
val game = GameEntity(
GameCardType.Game,
last.id
)
return PowerTag.PowerTaskList.CreateGame(
game,
player1!!,
player2!!
)
}
is NestedTag.Player -> {
keyValueMap["playerid"] = last.playerId.toString()
keyValueMap["entityid"] = last.entityId.toString()
if (player2 == null) {
player2 = Player.fromMap(keyValueMap)
} else {
player1 = Player.fromMap(keyValueMap)
}
keyValueMap.clear()
}
is NestedTag.Tag -> {
keyValueMap[last.key.replace("_", "").lowercase()] = last.value
}
else -> throw IllegalArgumentException("非法状态错误 $last")
}
last = nestedTagList.removeLastOrNull()
}
} else {
throw IllegalArgumentException("第一个必须是 CreateGame,但现在是 $first")
}
throw RuntimeException("无法创建CreateGame")
}
private fun flushBlock(
source: MutableList<NestedTag>,
): PowerTag.PowerTaskList.Block {
//有可能出现多级嵌套
val first = source.removeFirst() as? NestedTag.Block
if (first == null) {
throw RuntimeException("列表的第一个应该是Block,但现在是不是")
}
source.removeLast()
val payloads = mutableListOf<PowerTag>()
val tempList = mutableListOf<NestedTag>()
source.forEachIndexed { index, nestedTag ->
when (nestedTag) {
is NestedTag.Block -> {
// if (tempList.isEmpty()) {
tempList.add(nestedTag)
// } else {
// val blockStartCount = tempList.count {
// it is NestedTag.Block
// }
// if (blockStartCount != 1) {
// throw IllegalArgumentException("错误的block数量 $blockStartCount")
// }
//
// val pairedBlockEndIndex = findPairBlockEndIndex(source, index)
// if (pairedBlockEndIndex == -1) {
// throw IllegalArgumentException("找不到配对的结束标识")
// }
// val innerBlockList = source.subList(index, pairedBlockEndIndex)
// tempList.add(flushBlock(innerBlockList))
// }
}
is NestedTag.FullEntity -> {
if (tempList.isNotEmpty()) {
tempList.add(nestedTag)
} else {
payloads.add(PowerTag.PowerTaskList.FullEntity(nestedTag.entity))
}
}
is NestedTag.ShowEntity -> {
if (tempList.isNotEmpty()) {
tempList.add(nestedTag)
} else {
payloads.add(
PowerTag.PowerTaskList.ShowEntity(
nestedTag.entity, nestedTag.cardId
)
)
}
}
is NestedTag.Tag -> {
if (tempList.isNotEmpty()) {
tempList.add(nestedTag)
} else {
val last = payloads.last()
if (last is PowerTag.PowerTaskList.FullEntity) {
last.append(nestedTag.toPair())
} else if (last is PowerTag.PowerTaskList.ShowEntity) {
last.append(nestedTag.toPair())
}
}
}
is NestedTag.TagChange -> {
if (tempList.isNotEmpty()) {
tempList.add(nestedTag)
} else {
payloads.add(nestedTag.convert())
}
}
NestedTag.BlockEnd -> {
tempList.add(NestedTag.BlockEnd)
val blockStartCount = tempList.count {
it is NestedTag.Block
}
val blockEndCount = tempList.count {
it is NestedTag.BlockEnd
}
if (blockStartCount == blockEndCount) {
payloads.add(flushBlock(tempList))
tempList.clear()
}
}
else -> {
throw IllegalArgumentException("非法的数据 $nestedTag")
}
}
}
source.clear()
return PowerTag.PowerTaskList.Block(
first.blockType,
first.entity,
first.target,
payloads
)
}
}
private fun findPairBlockEndIndex(source: List<NestedTag>, start: Int): Int {
var blockStartCount = 0
source.subList(start, source.size).forEachIndexed { index, it ->
if (it is NestedTag.Block) {
blockStartCount++
} else if (it is NestedTag.BlockEnd) {
if (blockStartCount == 0) {
return index
}
blockStartCount--
}
}
return -1
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/parser/DeckFileObserver.kt
================================================
package com.ke.hs_tracker.core.parser
import com.ke.hs_tracker.core.entity.CurrentDeck
import com.ke.hs_tracker.core.removeTime
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.io.InputStream
/**
* deck文件观察者
*/
class DeckFileObserver(
private val interval: Long = 2000,
private val deckFileInputStreamProvider: suspend () -> InputStream?,
) {
private var oldLogSize = 0L
fun reset(){
oldLogSize = 0
}
suspend fun start(): Flow<CurrentDeck> = flow {
while (true) {
deckFileInputStreamProvider()?.reader()?.apply {
if (oldLogSize > 0) {
skip(oldLogSize)
}
val text = readText()
oldLogSize += text.length
val lines = text.lines()
.filter { it.isNotEmpty() }
listToDeck(lines)?.apply {
emit(this)
}
close()
}
delay(interval)
}
}
private fun listToDeck(list: List<String>): CurrentDeck? {
if (list.isEmpty()) {
return null
}
val contentList = list.map {
it.removeTime().third
}
val name = contentList.findLast {
it.startsWith("###", true)
} ?: return null
val target =
contentList.subList(contentList.indexOf(name), contentList.size).toMutableList()
target.removeFirst()
target.removeFirst()
val code = target.removeFirst()
return CurrentDeck(
name.replace("### ", ""), code
)
}
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/parser/PowerFileObserver.kt
================================================
package com.ke.hs_tracker.core.parser
import com.ke.hs_tracker.core.entity.CurrentDeck
import com.ke.hs_tracker.core.entity.PowerTag
import com.ke.hs_tracker.core.removeTime
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.io.InputStream
/**
* deck文件观察者
*/
class PowerFileObserver(
private val interval: Long = 2000,
private val fileInputStreamProvider: suspend () -> InputStream?,
) {
private var oldLogSize = 0L
fun reset(){
oldLogSize = 0
}
suspend fun start(): Flow<List<String>> = flow {
while (true) {
fileInputStreamProvider()?.reader()?.apply {
if (oldLogSize > 0) {
skip(oldLogSize)
}
val text = readText()
oldLogSize += text.length
val lines = text.lines()
.filter { it.isNotEmpty() }
emit(lines)
close()
}
delay(interval)
}
}
}
================================================
FILE: core/src/main/java/com/ke/hs_tracker/core/parser/PowerParser.kt
================================================
package com.ke.hs_tracker.core.parser
import com.ke.hs_tracker.core.entity.*
import com.ke.hs_tracker.core.entity.toCardType
import com.ke.hs_tracker.core.removeTime
/**
* 日志解析
*/
interface PowerParser {
/**
* 解析一行日志
*/
fun parse(content: String)
/**
* 解析结果监听
*/
var powerTagListener: ((PowerTag) -> Unit)?
}
class PowerParserImpl : PowerParser {
private val blockTagStack: BlockTagStack = BlockTagStackImpl()
override var powerTagListener: ((PowerTag) -> Unit)? = null
override fun parse(content: String) {
val pair = checkTypeAndReturnContent(content) ?: return
if (pair.first == LogType.PowerTaskList) {
handlePowerTaskListLog(pair.second)
} else {
handleGameStateLog(pair.second)
}
}
/**
* 检查日志类型并返回去掉时间和日期前缀的内容
*/
private fun checkTypeAndReturnContent(content: String): Pair<LogType, String>? {
if (content.length < TIME_PREFIX_SIZE) {
return null
}
val noTimeContent = content.substring(TIME_PREFIX_SIZE)
if (noTimeContent.startsWith(LogType.GameState.replace)) {
return LogType.GameState to noTimeContent.replace(LogType.GameState.replace, "").trim()
} else if (noTimeContent.startsWith(LogType.PowerTaskList.replace)) {
return LogType.PowerTaskList to noTimeContent.replace(LogType.PowerTaskList.replace, "")
.trim()
}
return null
}
private fun handlePowerTaskListLog(line: String) {
val result = blockTagStack.insert(line)
when (result) {
InsertStackResult.CanNotInsert -> {
handleUnSupportNestedTag(line)
}
is InsertStackResult.Over -> {
powerTagListener?.invoke(result.powerTag)
if (!result.handled) {
//需要自己处理
handleUnSupportNestedTag(line)
}
}
InsertStackResult.Success -> {
}
}
}
private fun handleUnSupportNestedTag(line: String) {
var matchResult = TAG_CHANGE_PATTERN.matchEntire(line)
if (matchResult != null) {
handleTagChangeLine(
matchResult.groupValues[1],
matchResult.groupValues[2],
matchResult.groupValues[3]
)
}
}
private fun handleTagChangeLine(content: String, tag: String, value: String) {
val entity = Entity.createFromContent(content)!!
powerTagListener?.invoke(PowerTag.PowerTaskList.TagChange(entity, tag, value))
}
private fun handleGameStateLog(content: String) {
BUILD_NUMBER_PATTERN.matchEntire(content)?.apply {
val tag = PowerTag.GameState.BuildNumber(
groupValues[1]
)
powerTagListener?.invoke(tag)
return
}
GAME_TYPE_PATTERN.matchEntire(content)?.apply {
val tag = PowerTag.GameState.GameType(
groupValues[1]
)
powerTagListener?.invoke(tag)
return
}
FORMAT_TYPE_PATTERN.matchEntire(content)?.apply {
val tag = PowerTag.GameState.FormatType(
groupValues[1]
)
powerTagListener?.invoke(tag)
return
}
SCENARIO_ID_PATTERN.matchEntire(content)?.apply {
val tag = PowerTag.GameState.ScenarioID(
groupValues[1]
)
powerTagListener?.invoke(tag)
return
}
PLAYER_MAPPING_PATTERN.matchEntire(content)?.apply {
val tag = PowerTag.GameState.PlayerMapping(
groupValues[1].toInt(), groupValues[2]
)
powerTagListener?.invoke(tag)
return
}
}
companion object {
const val TIME_PREFIX_SIZE = 19
//CREATE_GAME
internal val CREATE_GAME_PATTERN = Regex("CREATE_GAME")
//BuildNumber=127581
internal val BUILD_NUMBER_PATTERN = Regex("BuildNumber=(.*)")
//GameType=GT_CASUAL
internal val GAME_TYPE_PATTERN = Regex("GameType=(.*)")
//FormatType=FT_WILD
internal val FORMAT_TYPE_PATTERN = Regex("FormatType=(.*)")
//ScenarioID=2
internal val SCENARIO_ID_PATTERN = Regex("ScenarioID=(.*)")
//PlayerID=2, PlayerName=阿克萌德#51240
internal val PLAYER_MAPPING_PATTERN = Regex("PlayerID=(.*), PlayerName=(.*)")
// tag=CARDTYPE value=GAME
internal val TAG_PATTERN = Regex("tag=(.*) value=(.*)")
//TAG_CHANGE Entity=GameEntity tag=STATE value=RUNNING
internal val TAG_CHANGE_PATTERN = Regex("TAG_CHANGE Entity=(.*) tag=(.*) value=(.*)")
//GameEntity EntityID=1
internal val GAME_ENTITY_PATTERN = Regex("GameEntity EntityID=(.*)")
//FULL_ENTITY - Updating [entityName=加尔鲁什·地狱咆哮 id=64 zone=PLAY zonePos=0 cardId=HERO_01 player=1] CardID=HERO_01
internal val FULL_ENTITY_PATTERN = Regex("FULL_ENTITY - Updating (.*) CardID=(.*)")
//[entityName=UNKNOWN ENTITY [cardType=INVALID] id=83 zone=DECK zonePos=0 cardId= player=2]
val FULL_ENTITY_CONTENT1_PATTERN =
Regex("\\[entityName=(.*) \\[cardType=(.*)] id=(.*) zone=(.*) zonePos=(.*) cardId=(.*) player=(.*)]")
val FULL_ENTITY_CONTENT2_PATTERN =
Regex("\\[entityName=(.*) id=(.*) zone=(.*) zonePos=(.*) cardId=(.*) player=(.*)]")
val SHOW_ENTITY = Regex("SHOW_ENTITY - Updating Entity=(.*) CardID=(.*)")
//Player EntityID=2 PlayerID=1 GameAccountId=[hi=144115211015832391 lo=191215280]
internal val PLAYER_ENTITY_PATTERN =
Regex("Player EntityID=(.*) PlayerID=(.*) GameAccountId=(.*)")
//BLOCK_START BlockType=ATTACK Entity=[entityName=瓦丝琪女士 id=87 zone=PLAY zonePos=1 cardId=BT_109 player=2]
// EffectCardId=System.Collections.Generic.List`1[System.String]
// EffectIndex=0 Target=[entityName=驯化的雷象 id=78 zone=PLAY zonePos=1 cardId=SCH_714 player=1] SubOption=-1
internal val BLOCK_START_PATTERN =
Regex("BLOCK_START BlockType=(.*) Entity=(.*) EffectCardId=(.*) EffectIndex=(.*) Target=(.*) SubOption=(.*)")
internal val BLOCK_START_CONTINUATION_PATTERN = Regex("(.*) TriggerKeyword=(.*)")
//BLOCK_END
internal val BLOCK_END_PATTERN = Regex("BLOCK_END")
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Tue Jan 18 13:48:58 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
#android.jetifier.ignorelist=moshi-1.13.0
RELEASE_FILE=../123456
RELEASE_keyAlias=key0
RELEASE_storePassword=123456
RELEASE_keyPassword=123456
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: libs.versions.toml
================================================
[versions]
compilesdk = "33"
minsdk = "24"
targetsdk = "33"
retrofit = "2.9.0"
constraintlayout = "2.1.2"
material = "1.6.0"
moshi = "1.14.0"
ktx = "1.10.0"
hilt = "2.45"
lifecycle = "2.4.0"
mmkv = "1.2.11"
arouter = "1.5.2"
okhttp = "4.9.0"
navigation = "2.3.5"
logger = "2.2.0"
gson = "2.8.7"
appcompat = "1.6.1"
fragment = "1.5.5"
activity = "1.6.1"
support = "1.0.0"
hibinding = "1.2.0"
adapterhelper = "3.0.4"
pickerview = "4.1.9"
photoview = "2.3.0"
glide = "4.12.0"
latest = "latest.integration"
kehud = "1.3.5"
kepermission = "v1.0.0"
kemvvm = "1.1.3"
keimagepicker = "1.0.1"
kewechat = "1.1.8"
keqrscanner = "1.1"
keaddresspicker = "1.0.5"
keapkinstaller = "1.0.0"
kefilepicker = "1.0.0"
keidcard = "1.0.6"
room = "2.5.1"
[libraries]
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
material = { module = "com.google.android.material:material", version.ref = "material" }
constraint-layout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "ktx" }
navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment" }
activity = { module = "androidx.activity:activity", version.ref = "activity" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
support-v4 = { module = "androidx.legacy:legacy-support-v4", version.ref = "support" }
room-runtime = { module = "androidx.room:room-runtime",version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler",version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx",version.ref = "room" }
hilt-android = { module = "com.google.dagger:hilt-android" , version.ref = "hilt"}
hilt-compiler = { module = "com.google.dagger:hilt-compiler" , version.ref = "hilt"}
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
ke-mvvm = { module = "com.github.keluokeda:mvvm_base", version.ref = "kemvvm" }
logger = { module = "com.orhanobut:logger", version.ref = "logger" }
hi-binding = { module = "com.hi-dhl:binding", version.ref = "hibinding" }
mmkv = { module = "com.tencent:mmkv-static", version.ref = "mmkv" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
adapter-helper = { module = "com.github.CymChad:BaseRecyclerViewAdapterHelper", version.ref = "adapterhelper" }
================================================
FILE: module/.gitignore
================================================
/build
================================================
FILE: module/build.gradle
================================================
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
id 'kotlin-parcelize'
}
kapt {
correctErrorTypes true
}
android {
compileSdk libs.versions.compilesdk.get().toInteger()
defaultConfig {
minSdk libs.versions.minsdk.get().toInteger()
targetSdk libs.versions.targetsdk.get().toInteger()
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
javaCompileOptions {
annotationProcessorOptions {
arguments += [
"room.schemaLocation":"$projectDir/schemas".toString(),
"room.incremental":"true",
"room.expandProjection":"true"]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
resourcePrefix 'module_'
buildFeatures {
viewBinding = true
}
namespace 'com.ke.hs_tracker.module'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.junit.jupiter:junit-jupiter'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.constraint.layout)
implementation(libs.fragment.ktx)
implementation(libs.activity)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.support.v4)
implementation(libs.logger)
implementation(libs.hilt.android)
kapt(libs.hilt.compiler)
implementation(libs.glide)
kapt(libs.glide.compiler)
implementation(libs.ke.mvvm)
implementation(libs.adapter.helper)
implementation(libs.retrofit)
implementation(libs.retrofit.converter.moshi)
implementation(libs.moshi)
kapt(libs.moshi.codegen)
implementation(libs.hi.binding)
implementation(libs.mmkv)
implementation(libs.room.runtime)
implementation(libs.room.ktx)
kapt(libs.room.compiler)
// implementation 'com.tencent.bugly:crashreport:latest.release'
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// optional - helpers for implementing LifecycleOwner in a Service
implementation "androidx.lifecycle:lifecycle-service:2.6.1"
}
================================================
FILE: module/consumer-rules.pro
================================================
================================================
FILE: module/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: module/schemas/com.ke.hs_tracker.module.db.Database/1.json
================================================
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "d83bd3696913c5f4307118a1060d27e4",
"entities": [
{
"tableName": "card",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `name` TEXT NOT NULL, `cost` INTEGER NOT NULL, `id` TEXT NOT NULL, `dbfId` INTEGER NOT NULL, `text` TEXT NOT NULL, `set` TEXT NOT NULL, `type` TEXT NOT NULL, `cardClass` TEXT, `classes` INTEGER NOT NULL, `mechanics` TEXT NOT NULL, `flavor` TEXT NOT NULL, `rarity` TEXT, `durability` INTEGER NOT NULL, `armor` INTEGER NOT NULL, `collectible` INTEGER NOT NULL, `spellSchool` TEXT, `race` TEXT, `attach` INTEGER NOT NULL, `health` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cost",
"columnName": "cost",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dbfId",
"columnName": "dbfId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "set",
"columnName": "set",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cardClass",
"columnName": "cardClass",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "classes",
"columnName": "classes",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mechanics",
"columnName": "mechanics",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "flavor",
"columnName": "flavor",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "rarity",
"columnName": "rarity",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "durability",
"columnName": "durability",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "armor",
"columnName": "armor",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectible",
"columnName": "collectible",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "spellSchool",
"columnName": "spellSchool",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "race",
"columnName": "race",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attach",
"columnName": "attach",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "health",
"columnName": "health",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "game",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `build_number` TEXT NOT NULL, `game_type` TEXT NOT NULL, `format_type` TEXT NOT NULL, `scenario_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `opponent_name` TEXT NOT NULL, `is_user_first` INTEGER, `user_deck_name` TEXT NOT NULL, `user_deck_code` TEXT NOT NULL, `is_user_win` INTEGER, `user_hero` TEXT, `opponent_class` TEXT, `start_time` INTEGER NOT NULL, `end_time` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "buildNumber",
"columnName": "build_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gameType",
"columnName": "game_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formatType",
"columnName": "format_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "scenarioID",
"columnName": "scenario_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userName",
"columnName": "user_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "opponentName",
"columnName": "opponent_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserFirst",
"columnName": "is_user_first",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userDeckName",
"columnName": "user_deck_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userDeckCode",
"columnName": "user_deck_code",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserWin",
"columnName": "is_user_win",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userHero",
"columnName": "user_hero",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "opponentHero",
"columnName": "opponent_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "startTime",
"columnName": "start_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endTime",
"columnName": "end_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd83bd3696913c5f4307118a1060d27e4')"
]
}
}
================================================
FILE: module/schemas/com.ke.hs_tracker.module.db.Database/2.json
================================================
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "1bb7b04a91def09f83b0a81252d181ba",
"entities": [
{
"tableName": "card",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `name` TEXT NOT NULL, `cost` INTEGER NOT NULL, `id` TEXT NOT NULL, `dbfId` INTEGER NOT NULL, `text` TEXT NOT NULL, `set` TEXT NOT NULL, `type` TEXT NOT NULL, `cardClass` TEXT, `classes` INTEGER NOT NULL, `mechanics` TEXT NOT NULL, `flavor` TEXT NOT NULL, `rarity` TEXT, `durability` INTEGER NOT NULL, `armor` INTEGER NOT NULL, `collectible` INTEGER NOT NULL, `spellSchool` TEXT, `race` TEXT, `attach` INTEGER NOT NULL, `health` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cost",
"columnName": "cost",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dbfId",
"columnName": "dbfId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "set",
"columnName": "set",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cardClass",
"columnName": "cardClass",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "classes",
"columnName": "classes",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mechanics",
"columnName": "mechanics",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "flavor",
"columnName": "flavor",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "rarity",
"columnName": "rarity",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "durability",
"columnName": "durability",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "armor",
"columnName": "armor",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectible",
"columnName": "collectible",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "spellSchool",
"columnName": "spellSchool",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "race",
"columnName": "race",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attach",
"columnName": "attach",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "health",
"columnName": "health",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "game",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `build_number` TEXT NOT NULL, `game_type` TEXT NOT NULL, `format_type` TEXT NOT NULL, `scenario_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `opponent_name` TEXT NOT NULL, `is_user_first` INTEGER, `user_deck_name` TEXT NOT NULL, `user_deck_code` TEXT NOT NULL, `is_user_win` INTEGER, `user_hero` TEXT, `opponent_class` TEXT, `start_time` INTEGER NOT NULL, `end_time` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "buildNumber",
"columnName": "build_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gameType",
"columnName": "game_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formatType",
"columnName": "format_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "scenarioID",
"columnName": "scenario_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userName",
"columnName": "user_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "opponentName",
"columnName": "opponent_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserFirst",
"columnName": "is_user_first",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userDeckName",
"columnName": "user_deck_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userDeckCode",
"columnName": "user_deck_code",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserWin",
"columnName": "is_user_win",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userHero",
"columnName": "user_hero",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "opponentHero",
"columnName": "opponent_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "startTime",
"columnName": "start_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endTime",
"columnName": "end_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "in_game_card",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `build_number` TEXT NOT NULL, `card_id` TEXT, `entity_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `zone` TEXT NOT NULL, `turn` INTEGER NOT NULL, `is_user` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "buildNumber",
"columnName": "build_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cardId",
"columnName": "card_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "zone",
"columnName": "zone",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "turn",
"columnName": "turn",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isUser",
"columnName": "is_user",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1bb7b04a91def09f83b0a81252d181ba')"
]
}
}
================================================
FILE: module/schemas/com.ke.hs_tracker.module.db.Database/3.json
================================================
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "720ca460a50c2d8a7f6bf96c98cf4358",
"entities": [
{
"tableName": "card",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `name` TEXT NOT NULL, `cost` INTEGER NOT NULL, `id` TEXT NOT NULL, `dbfId` INTEGER NOT NULL, `text` TEXT NOT NULL, `set` TEXT NOT NULL, `type` TEXT NOT NULL, `cardClass` TEXT, `classes` INTEGER NOT NULL, `mechanics` TEXT NOT NULL, `flavor` TEXT NOT NULL, `rarity` TEXT, `durability` INTEGER NOT NULL, `armor` INTEGER NOT NULL, `collectible` INTEGER NOT NULL, `spellSchool` TEXT, `race` TEXT, `attach` INTEGER NOT NULL, `health` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cost",
"columnName": "cost",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dbfId",
"columnName": "dbfId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "set",
"columnName": "set",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cardClass",
"columnName": "cardClass",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "classes",
"columnName": "classes",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mechanics",
"columnName": "mechanics",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "flavor",
"columnName": "flavor",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "rarity",
"columnName": "rarity",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "durability",
"columnName": "durability",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "armor",
"columnName": "armor",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectible",
"columnName": "collectible",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "spellSchool",
"columnName": "spellSchool",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "race",
"columnName": "race",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attach",
"columnName": "attach",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "health",
"columnName": "health",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "game",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `build_number` TEXT NOT NULL, `game_type` TEXT NOT NULL, `format_type` TEXT NOT NULL, `scenario_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `opponent_name` TEXT NOT NULL, `is_user_first` INTEGER, `user_deck_name` TEXT NOT NULL, `user_deck_code` TEXT NOT NULL, `is_user_win` INTEGER, `user_hero` TEXT, `opponent_class` TEXT, `start_time` INTEGER NOT NULL, `end_time` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "buildNumber",
"columnName": "build_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gameType",
"columnName": "game_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formatType",
"columnName": "format_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "scenarioID",
"columnName": "scenario_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userName",
"columnName": "user_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "opponentName",
"columnName": "opponent_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserFirst",
"columnName": "is_user_first",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userDeckName",
"columnName": "user_deck_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userDeckCode",
"columnName": "user_deck_code",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserWin",
"columnName": "is_user_win",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userHero",
"columnName": "user_hero",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "opponentHero",
"columnName": "opponent_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "startTime",
"columnName": "start_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endTime",
"columnName": "end_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "zone_position_updated_event",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `game_id` TEXT NOT NULL, `entity_id` INTEGER NOT NULL, `card_id` TEXT, `card_name` TEXT, `is_user` INTEGER NOT NULL, `current_zone` TEXT NOT NULL, `new_zone` TEXT NOT NULL, `current_position` INTEGER NOT NULL, `new_position` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gameId",
"columnName": "game_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "cardId",
"columnName": "card_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "cardName",
"columnName": "card_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isUser",
"columnName": "is_user",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "currentZone",
"columnName": "current_zone",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "newZone",
"columnName": "new_zone",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentPosition",
"columnName": "current_position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "newPosition",
"columnName": "new_position",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '720ca460a50c2d8a7f6bf96c98cf4358')"
]
}
}
================================================
FILE: module/schemas/com.ke.hs_tracker.module.db.Database/4.json
================================================
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "a2944b7e136b6abe232a7a446c5ea3db",
"entities": [
{
"tableName": "card",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`artist` TEXT, `name` TEXT NOT NULL, `cost` INTEGER NOT NULL, `id` TEXT NOT NULL, `dbfId` INTEGER NOT NULL, `text` TEXT NOT NULL, `set` TEXT NOT NULL, `type` TEXT NOT NULL, `cardClass` TEXT, `classes` INTEGER NOT NULL, `mechanics` TEXT NOT NULL, `flavor` TEXT NOT NULL, `rarity` TEXT, `durability` INTEGER NOT NULL, `armor` INTEGER NOT NULL, `collectible` INTEGER NOT NULL, `spellSchool` TEXT, `race` TEXT, `attack` INTEGER NOT NULL, `health` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cost",
"columnName": "cost",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dbfId",
"columnName": "dbfId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "set",
"columnName": "set",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "cardClass",
"columnName": "cardClass",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "classes",
"columnName": "classes",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mechanics",
"columnName": "mechanics",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "flavor",
"columnName": "flavor",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "rarity",
"columnName": "rarity",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "durability",
"columnName": "durability",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "armor",
"columnName": "armor",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "collectible",
"columnName": "collectible",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "spellSchool",
"columnName": "spellSchool",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "race",
"columnName": "race",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attack",
"columnName": "attack",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "health",
"columnName": "health",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "game",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `build_number` TEXT NOT NULL, `game_type` TEXT NOT NULL, `format_type` TEXT NOT NULL, `scenario_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `opponent_name` TEXT NOT NULL, `is_user_first` INTEGER, `user_deck_name` TEXT NOT NULL, `user_deck_code` TEXT NOT NULL, `is_user_win` INTEGER, `user_hero` TEXT, `opponent_class` TEXT, `start_time` INTEGER NOT NULL, `end_time` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "buildNumber",
"columnName": "build_number",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gameType",
"columnName": "game_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formatType",
"columnName": "format_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "scenarioID",
"columnName": "scenario_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userName",
"columnName": "user_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "opponentName",
"columnName": "opponent_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserFirst",
"columnName": "is_user_first",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userDeckName",
"columnName": "user_deck_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "userDeckCode",
"columnName": "user_deck_code",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isUserWin",
"columnName": "is_user_win",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "userHero",
"columnName": "user_hero",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "opponentHero",
"columnName": "opponent_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "startTime",
"columnName": "start_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endTime",
"columnName": "end_time",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "zone_position_updated_event",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `game_id` TEXT NOT NULL, `entity_id` INTEGER NOT NULL, `card_id` TEXT, `card_name` TEXT, `is_user` INTEGER NOT NULL, `current_zone` TEXT NOT NULL, `new_zone` TEXT NOT NULL, `current_position` INTEGER NOT NULL, `new_position` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "gameId",
"columnName": "game_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "cardId",
"columnName": "card_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "cardName",
"columnName": "card_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isUser",
"columnName": "is_user",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "currentZone",
"columnName": "current_zone",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "newZone",
"columnName": "new_zone",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "currentPosition",
"columnName": "current_position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "newPosition",
"columnName": "new_position",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a2944b7e136b6abe232a7a446c5ea3db')"
]
}
}
================================================
FILE: module/src/androidTest/java/com/ke/hs_tracker/module/ExampleInstrumentedTest.kt
================================================
package com.ke.hs_tracker.module
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.ke.hs_tracker.module.test", appContext.packageName)
}
}
================================================
FILE: module/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/module_app_name"
android:resizeableActivity="true"
android:largeHeap="true"
android:theme="@style/module_Theme.Hs_tracker">
<activity
android:name=".ui.support.SupportActivity"
android:exported="false">
</activity>
<service
android:name=".service.WindowService"
android:enabled="true"
android:exported="false" />
<activity
android:name=".ui.deckbattledetail.DeckBattleDetailActivity"
android:exported="false" />
<activity
android:name=".ui.classbattledetail.ClassBattleDetailActivity"
android:exported="false" />
<activity
android:name=".ui.chart.SummaryChartActivity"
android:exported="false" />
<activity
android:name=".ui.migrate.SocketClientActivity"
android:exported="false" />
<activity
android:name=".ui.migrate.SocketServerActivity"
android:exported="false" />
<activity
android:name=".ui.migrate.MigrateMainActivity"
android:exported="false" />
<activity
android:name=".ui.summary.SummaryActivity"
android:exported="false" />
<activity
android:name=".ui.test.LocalFileParserActivity"
android:exported="false" />
<activity
android:name=".ui.zonecards.ZoneCardsActivity"
android:exported="false" />
<activity
android:name=".ui.zoneevents.ZoneEventsActivity"
android:exported="false" />
<activity
android:name=".ui.test.CreateRecordActivity"
android:exported="false" />
<activity
android:name=".ui.records.RecordsActivity"
android:exported="false" />
<activity
android:name=".ui.writeconfig.WriteConfigActivity"
android:exported="false" />
<activity
android:name=".ui.diagnose.DiagnoseActivity"
android:exported="false" />
<activity
android:name=".ui.theme.ThemeActivity"
android:exported="false" />
<activity
android:name=".ui.filter.FilterActivity"
android:exported="false" />
<activity
android:name=".ui.test.TestActivity"
android:exported="false" />
<activity
android:name=".ui.settings.SettingsActivity"
android:exported="false" />
<activity
android:name=".ui.main.MainActivity"
android:exported="false"
android:resizeableActivity="true"
android:theme="@style/module_Theme.Hs_tracker.Main" />
<activity
android:name=".ui.deck.DeckCodeParserActivity"
android:exported="false" />
<activity
android:name=".ui.sync.SyncCardDataActivity"
android:exported="false" />
<activity
android:name=".ui.permissions.PermissionsActivity"
android:exported="false" />
<activity
android:name=".ui.splash.SplashActivity"
android:exported="true"
android:theme="@style/module_Theme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: module/src/main/assets/Decks.log
================================================
I 22:23:04.0780690 Deck Contents Received:
I 22:23:04.0780690 ### 狗贼
I 22:23:04.0780690 # Deck ID: 7996212750
I 22:23:04.0780690 AAEBAZICCukB6awCtLsC1pkDiLED6LoDndgDv+ADpu8DiYsECooOoM0CmNICntIChOYCv/ICj/YCm84D1OgDr4AEAA==
I 22:23:04.0780690 ### 法术
I 22:23:04.0780690 # Deck ID: 7997355263
I 22:23:04.0780690 AAECAf0ECIy5A+DMA7/gA5PhA7L3A6CKBJiNBPyeBAvBuAOBvwPNzgP73QPr3gPQ7APR7AOn9wOKjQT9ngT7ogQA
I 22:23:04.0780690 ### 自定义 潜行者
I 22:23:04.0780690 # Deck ID: 8003374434
I 22:23:04.0780690 AAECAaIHAAAA
I 22:23:04.0780690 ### 小丑
I 22:23:04.0780690 # Deck ID: 8004144061
I 22:23:04.0780690 AAECAZICBrrQA/zeA6bvA9D5A4mLBOSkBAzougObzgPw1AP+2wOJ4AOV4AOi4QOk4QPR4QPm4QPe7AOvgAQA
I 22:23:04.0780690 ### 双古神
I 22:23:04.0780690 # Deck ID: 8005951524
I 22:23:04.0780690 AAECAZICCO66A53YA/zeA4rgA6bvA9D5A4mLBKWNBAvougObzgPw1AOJ4AOV4AOi4QOk4QPR4QPm4QOP5AOvgAQA
I 22:23:04.0780690 ### 圣契骑
I 22:23:04.0780690 # Deck ID: 8006192896
I 22:23:04.0780690 AAECAZ8FDOu5A+u5A+y5A+y5A4TBA5XNA5PQA7/RA/voA5HsA9n5A+CLBAn9uAPquQPKwQPA0QPM6wPw9gON+AO2gAT5pAQA
I 22:23:04.0780690 ### 对决套牌
I 22:23:04.0780690 # Deck ID: 8009171311
I 22:23:04.0780690 AAEBAbLwAw+WBc3OA6TRA9nRA7fSA4vnA9DsA9HsA6f3A673A6CKBIqNBP2eBMWgBPuiBAAA
I 22:23:04.0780690 ### 任务
I 22:23:04.0780690 # Deck ID: 8010668041
I 22:23:04.0780690 AAECAaoIBJzOA6beA8L2A9ySBA3buAPhzAPNzgPw1AOK5APq5wP67APk9gOF+gOogQSVkgT5nwT6nwQA
I 22:23:04.0780690 ### 卡扎库
I 22:23:04.0780690 # Deck ID: 8020766665
I 22:23:04.0780690 AAECAbr5AwPR4QOJiwSJnwQN5boD6LoD77oDm84D8NQDieADiuADjOQDj+QDr4AErp8EsKUEz6wEAA==
I 22:23:06.5584800 Deck Contents Received:
I 22:23:06.5584800 ### 狗贼
I 22:23:06.5584800 # Deck ID: 7996212750
I 22:23:06.5584800 AAEBAZICCukB6awCtLsC1pkDiLED6LoDndgDv+ADpu8DiYsECooOoM0CmNICntIChOYCv/ICj/YCm84D1OgDr4AEAA==
I 22:23:06.5584800 ### 法术
I 22:23:06.5584800 # Deck ID: 7997355263
I 22:23:06.5584800 AAECAf0ECIy5A+DMA7/gA5PhA7L3A6CKBJiNBPyeBAvBuAOBvwPNzgP73QPr3gPQ7APR7AOn9wOKjQT9ngT7ogQA
I 22:23:06.5584800 ### 自定义 潜行者
I 22:23:06.5584800 # Deck ID: 8003374434
I 22:23:06.5584800 AAECAaIHAAAA
I 22:23:06.5584800 ### 小丑
I 22:23:06.5584800 # Deck ID: 8004144061
I 22:23:06.5584800 AAECAZICBrrQA/zeA6bvA9D5A4mLBOSkBAzougObzgPw1AP+2wOJ4AOV4AOi4QOk4QPR4QPm4QPe7AOvgAQA
I 22:23:06.5584800 ### 双古神
I 22:23:06.5584800 # Deck ID: 8005951524
I 22:23:06.5584800 AAECAZICCO66A53YA/zeA4rgA6bvA9D5A4mLBKWNBAvougObzgPw1AOJ4AOV4AOi4QOk4QPR4QPm4QOP5AOvgAQA
I 22:23:06.5584800 ### 圣契骑
I 22:23:06.5584800 # Deck ID: 8006192896
I 22:23:06.5584800 AAECAZ8FDOu5A+u5A+y5A+y5A4TBA5XNA5PQA7/RA/voA5HsA9n5A+CLBAn9uAPquQPKwQPA0QPM6wPw9gON+AO2gAT5pAQA
I 22:23:06.5584800 ### 对决套牌
I 22:23:06.5584800 # Deck ID: 8009171311
I 22:23:06.5584800 AAEBAbLwAw+WBc3OA6TRA9nRA7fSA4vnA9DsA9HsA6f3A673A6CKBIqNBP2eBMWgBPuiBAAA
I 22:23:06.5584800 ### 任务
I 22:23:06.5584800 # Deck ID: 8010668041
I 22:23:06.5584800 AAECAaoIBJzOA6beA8L2A9ySBA3buAPhzAPNzgPw1AOK5APq5wP67APk9gOF+gOogQSVkgT5nwT6nwQA
I 22:23:06.5584800 ### 卡扎库
I 22:23:06.5584800 # Deck ID: 8020766665
I 22:23:06.5584800 AAECAbr5AwPR4QOJiwSJnwQN5boD6LoD77oDm84D8NQDieADiuADjOQDj+QDr4AErp8EsKUEz6wEAA==
I 22:23:35.6401730 Finding Game With Deck:
I 22:23:35.6401730 ### 任务
I 22:23:35.6401730 # Deck ID: 8010668041
I 22:23:35.6401730 AAECAaoIBJzOA6beA8L2A9ySBA3buAPhzAPNzgPw1AOK5APq5wP67APk9gOF+gOogQSVkgT5nwT6nwQA
================================================
FILE: module/src/main/assets/log.config
================================================
[Bob]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Power]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Achievements]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Arena]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[FullScreenFX]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[LoadingScreen]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Rachelle]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Asset]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Zone]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
[Decks]
LogLevel=1
FilePrinting=True
ConsolePrinting=False
ScreenPrinting=False
Verbose=True
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/MainApplication.kt
================================================
package com.ke.hs_tracker.module
import android.app.Application
import android.content.Context
import android.net.Uri
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.res.ResourcesCompat
import androidx.documentfile.provider.DocumentFile
import com.bumptech.glide.Glide
import com.ke.hs_tracker.module.data.PreferenceStorage
import com.ke.hs_tracker.module.databinding.ModuleDialogCardPreviewBinding
import com.ke.hs_tracker.module.databinding.ModuleItemCardBinding
import com.ke.hs_tracker.module.entity.Card
import com.ke.hs_tracker.module.parser.PowerParserImpl
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.Logger
import com.orhanobut.logger.PrettyFormatStrategy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Calendar
import java.util.Date
import javax.inject.Inject
abstract class MainApplication : Application() {
@Inject
lateinit var preferenceStorage: PreferenceStorage
override fun onCreate() {
super.onCreate()
// CrashReport.initCrashReport(applicationContext, "abb84be20b", false)
AppCompatDelegate.setDefaultNightMode(preferenceStorage.theme)
Logger.addLogAdapter(
AndroidLogAdapter(
PrettyFormatStrategy.newBuilder()
.methodCount(5)
.build()
)
)
}
}
fun String.removeTime(): Triple<String, Date, String> {
val content = substring(PowerParserImpl.TIME_PREFIX_SIZE)
val start = substring(0, 1)
val hms = substring(2, 10).split(":")
val calendar = Calendar.getInstance()
calendar.set(
Calendar.HOUR_OF_DAY, hms[0].toInt()
)
calendar.set(
Calendar.MINUTE, hms[1].toInt()
)
calendar.set(
Calendar.SECOND, hms[2].toInt()
)
return Triple(
start,
calendar.time,
content
)
}
fun String.log() {
Logger.d(this)
}
/**
* 是否具备所有权限
*/
val Context.hasAllPermissions: Boolean
get() {
// val canWriteExternalStorage = ActivityCompat.checkSelfPermission(
// this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE
// ) == PackageManager.PERMISSION_GRANTED
//
// return canWriteExternalStorage && canReadDataDir && isExternalStorageManager()
return canReadDataDir
}
/**
* 是否可以访问data目录
*/
val Context.canReadDataDir: Boolean
get() {
return DocumentFile.fromTreeUri(applicationContext, HS_DATA_FILE_DIR)?.canRead() ?: false
}
/**
* 是否具备外部存储管理权限,如果android版本低于11就返回true
*/
//fun isExternalStorageManager(): Boolean {
//
// return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Environment.isExternalStorageManager()
// } else {
// return true
// }
//}
const val HS_APPLICATION_ID = "com.blizzard.wtcg.hearthstone"
val HS_DATA_FILE_DIR =
Uri.parse("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata%2F${HS_APPLICATION_ID}")!!
//const val HUAWEI_HS_APPLICATION_ID = "com.blizzard.wtcg.hearthstone.cn.huawei"
/**
* 写入log.config文件
*/
suspend fun Context.writeLogConfigFile(forceWrite: Boolean = false): Boolean {
return withContext(Dispatchers.IO) {
val documentFile = findHSDataFilesDir() ?: return@withContext false
val fileName = "log.config"
val file = findHSDataFilesDir(fileName)
if (file != null) {
//文件已存在
"log.config文件已存在".log()
if (forceWrite) {
file.delete()
} else {
return@withContext true
}
}
val configFile = documentFile.createFile("plain/text", fileName)
?: return@withContext false
contentResolver.openOutputStream(configFile.uri)?.use {
assets.open("log.config")
.copyTo(it)
it.flush()
}
return@withContext true
}
}
//通过从根目录进入的方式可以创建文件
fun Context.findHSDataFilesDir(
fileName: String? = null,
): DocumentFile? {
DocumentFile.fromTreeUri(
applicationContext,
HS_DATA_FILE_DIR
)?.apply {
// listFiles().forEach {
// if (it.name == applicationId) {
val filesDir = this.findFile("files") ?: return null
return if (fileName == null) filesDir else filesDir.findFile(
fileName
)
// }
// }
}
return null
// if (applicationId == HUAWEI_HS_APPLICATION_ID) {
// return null
// }
//
// return findHSDataFilesDir(fileName, HUAWEI_HS_APPLICATION_ID)
}
fun ModuleItemCardBinding.bindCard(card: Card) {
name.text = card.name
cost.text = card.cost.toString()
card.rarity?.apply {
this@bindCard.cost.setBackgroundColor(
ResourcesCompat.getColor(
root.context.resources,
colorRes,
null
)
)
}
Glide.with(imageTile)
.load("https://art.hearthstonejson.com/v1/tiles/${card.id}.png")
.into(imageTile)
}
fun showCardImageDialog(context: Context, cardId: String) {
val binding = ModuleDialogCardPreviewBinding.inflate(LayoutInflater.from(context))
AlertDialog.Builder(context)
.show().apply {
window?.run {
setContentView(binding.root)
binding.root.setOnClickListener {
dismiss()
}
//去掉对话框的白色背景
setBackgroundDrawableResource(android.R.color.transparent)
}
}
Glide.with(binding.image)
.load("https://art.hearthstonejson.com/v1/render/latest/zhCN/512x/${cardId}.png")
.placeholder(R.mipmap.ic_launcher)
.into(binding.image)
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/api/HearthStoneJsonApi.kt
================================================
package com.ke.hs_tracker.module.api
import com.ke.hs_tracker.module.entity.Card
import retrofit2.http.GET
import retrofit2.http.Path
interface HearthStoneJsonApi {
/**
* 获取卡牌数据
*/
@GET("v1/{versionCode}/{region}/cards.json")
suspend fun getCardJsonList(
@Path("versionCode") versionCode: String,
@Path("region") region: String,
): List<Card>
companion object {
const val BASE_URL = "https://api.hearthstonejson.com/"
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/data/PreferenceStorage.kt
================================================
package com.ke.hs_tracker.module.data
import android.content.Context
import androidx.appcompat.app.AppCompatDelegate
import com.tencent.mmkv.MMKV
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
interface PreferenceStorage {
var theme: Int
/**
* 保存日志文件
*/
var saveLogFile: Boolean
var floatingEnable: Boolean
var crash: String?
}
class PreferenceStorageImpl @Inject constructor(
@ApplicationContext private val context: Context
) : PreferenceStorage {
init {
MMKV.initialize(context)
}
private val mmkv = MMKV.defaultMMKV()
override var theme: Int
get() = mmkv.getInt(KEY_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
set(value) {
AppCompatDelegate.setDefaultNightMode(value)
mmkv.encode(KEY_THEME, value)
}
override var saveLogFile: Boolean
get() = mmkv.getBoolean(KEY_SAVE_LOG_FILE, false)
set(value) {
mmkv.encode(KEY_SAVE_LOG_FILE, value)
}
override var floatingEnable: Boolean
get() = mmkv.getBoolean(KEY_FLOATING_ENABLE, true)
set(value) {
mmkv.putBoolean(KEY_FLOATING_ENABLE, value)
}
override var crash: String?
get() = mmkv.getString(KEY_CRASH, null)
set(value) {
mmkv.putString(KEY_CRASH, value)
}
companion object {
private const val KEY_THEME = "KEY_THEME"
private const val KEY_SAVE_LOG_FILE = "KEY_SAVE_LOG_FILE"
private const val KEY_FLOATING_ENABLE = "KEY_FLOATING_ENABLE"
private const val KEY_CRASH = "KEY_CRASH"
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/CardClassesConvert.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.TypeConverter
import com.ke.hs_tracker.module.entity.CardClass
class CardClassesConvert {
@TypeConverter
fun longToClasses(value: Long?): List<CardClass> {
if (value == null) {
return emptyList()
}
return CardClass.values()
.map {
it to (1L shl it.ordinal)
}
.filter {
it.second and value == it.second
}
.map { it.first }
}
@TypeConverter
fun classesToLong(list: List<CardClass>): Long {
if (list.isEmpty()) {
return 0
}
var result = 0L
list.map {
1L shl it.ordinal
}.forEach {
result = result or it
}
return result
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/CardDao.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import com.ke.hs_tracker.module.entity.Card
@Dao
interface CardDao {
@Query("select * from card")
suspend fun getAll(): List<Card>
@Insert
suspend fun insert(list: List<Card>)
@Query("delete from card")
suspend fun deleteAll()
@Query("select COUNT(id) from card")
suspend fun getCount(): Int
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/Database.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.*
import androidx.room.Database
import androidx.room.migration.AutoMigrationSpec
import com.ke.hs_tracker.module.entity.Card
const val DATABASE_VERSION = 4
@Database(
entities = [Card::class, Game::class, ZonePositionChangedEvent::class],
version = DATABASE_VERSION,
exportSchema = true,
autoMigrations = [
AutoMigration(
from = 3,
to = 4,
spec = com.ke.hs_tracker.module.db.Database.RenameAttachToAttackMigration::class
)
]
)
@TypeConverters(
CardClassesConvert::class, MechanicsListConvert::class
)
abstract class Database : RoomDatabase() {
@RenameColumn(tableName = "card", fromColumnName = "attach", toColumnName = "attack")
class RenameAttachToAttackMigration : AutoMigrationSpec
abstract fun cardDao(): CardDao
abstract fun gameDao(): GameDao
abstract fun zonePositionChangedEventDao(): ZonePositionChangedEventDao
companion object {
const val VERSION = 3
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/Game.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.ke.hs_tracker.module.entity.CardClass
import com.ke.hs_tracker.module.entity.FormatType
import com.ke.hs_tracker.module.entity.GameType
import com.squareup.moshi.JsonClass
import java.util.*
@JsonClass(generateAdapter = true)
@Entity(tableName = "game")
data class Game(
@PrimaryKey(autoGenerate = false)
val id: String = UUID.randomUUID().toString(),
//并不是唯一标识
@ColumnInfo(name = "build_number")
val buildNumber: String = "",
@ColumnInfo(name = "game_type")
var gameType: GameType = GameType.Unknown,
@ColumnInfo(name = "format_type")
var formatType: FormatType = FormatType.Unknown,
@ColumnInfo(name = "scenario_id")
var scenarioID: Int = 0,
@ColumnInfo(name = "user_name")
var userName: String = "",
@ColumnInfo(name = "opponent_name")
var opponentName: String = "",
@ColumnInfo(name = "is_user_first")
var isUserFirst: Boolean? = null,
@ColumnInfo(name = "user_deck_name")
var userDeckName: String = "",
@ColumnInfo(name = "user_deck_code")
var userDeckCode: String = "",
@ColumnInfo(name = "is_user_win")
var isUserWin: Boolean? = null,
@ColumnInfo(name = "user_hero")
var userHero: CardClass? = null,
@ColumnInfo(name = "opponent_class")
var opponentHero: CardClass? = null,
@ColumnInfo(name = "start_time")
var startTime: Long = 0,
@ColumnInfo(name = "end_time")
var endTime: Long = 0
) {
// val userName: String
// get() = if (isUserFirst == true) player1Name else player2Name
// var opponentName: String
// get() = if (isUserFirst == true) player2Name else player1Name
// set(value) {
// if (isUserFirst == true) {
// player2Name = value
// } else {
// player1Name = value
// }
// }
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/GameDao.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.*
import com.ke.hs_tracker.module.entity.CardClass
@Dao
interface GameDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(game: Game)
/**
* 插入所有
*/
@Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(games: List<Game>)
@Update
suspend fun update(game: Game)
/**
* 删除全部
*/
@Query("delete from game")
suspend fun deleteAll()
/**
* 查询所有
*/
@Query("select * from game")
suspend fun getAll(): List<Game>
/**
* 查找用户某个英雄的总对局
*/
@Query("select * from game where user_hero = :cardClass")
suspend fun getByHero(cardClass: CardClass): List<Game>
/**
* 获取总的对局数
*/
@Query("select count(*) from game")
suspend fun getGameCount(): Int
/**
* 获取玩家胜率对局数
*/
@Query("select count(*) from game where is_user_win = 1")
suspend fun getUserWinCount(): Int
/**
* 根据卡组代码和名称查询
*/
@Query("select * from game where user_deck_code = :code and user_deck_name = :name")
suspend fun getByDeckCodeAndName(
code: String,
name: String
): List<Game>
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/MechanicsListConvert.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.TypeConverter
import com.ke.hs_tracker.module.entity.CardClass
import com.ke.hs_tracker.module.entity.Mechanics
import com.ke.hs_tracker.module.entity.MechanicsAdapter
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import org.json.JSONArray
import javax.inject.Inject
class MechanicsListConvert {
@TypeConverter
fun stringToClasses(value: String?): List<Mechanics> {
if (value == null) {
return emptyList()
}
val result = mutableListOf<Mechanics>()
val jsonArray = JSONArray(value)
val mechanicsAdapter = MechanicsAdapter()
val size = jsonArray.length()
for (index in 0 until size) {
val m = mechanicsAdapter.fromJson(jsonArray.get(index).toString())
result.add(m)
}
return result
// return Mechanics.values()
// .map {
// it to (1L shl it.ordinal)
// }
// .filter {
// it.second and value == it.second
// }
// .map { it.first }
}
@TypeConverter
fun classesToString(list: List<Mechanics>): String {
val jsonArray = JSONArray()
if (list.isEmpty()) {
return jsonArray.toString()
}
list.forEach {
jsonArray.put(it.name)
}
return jsonArray.toString()
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/ZonePositionChangedEvent.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.ke.hs_tracker.module.entity.Zone
import com.squareup.moshi.JsonClass
import java.util.*
@JsonClass(generateAdapter = true)
@Entity(tableName = "zone_position_updated_event")
data class ZonePositionChangedEvent(
@PrimaryKey(autoGenerate = false)
val id: String = UUID.randomUUID().toString(),
@ColumnInfo(name = "game_id")
var gameId: String = "",
@ColumnInfo(name = "entity_id")
val entityId: Int,
@ColumnInfo(name = "card_id")
var cardId: String?,
@ColumnInfo(name = "card_name")
var cardName: String? = null,
@ColumnInfo(name = "is_user")
var isUser: Boolean = false,
@ColumnInfo(name = "current_zone")
var currentZone: Zone,
@ColumnInfo(name = "new_zone")
var newZone: Zone,
@ColumnInfo(name = "current_position")
val currentPosition: Int,
@ColumnInfo(name = "new_position")
val newPosition: Int
) {
//TAG_CHANGE Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=63 zone=DECK zonePos=0 cardId= player=2] tag=ZONE value=HAND
//TAG_CHANGE Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=63 zone=DECK zonePos=0 cardId= player=2] tag=ZONE_POSITION value=1
fun plus(event: ZonePositionChangedEvent): ZonePositionChangedEvent {
if (event.entityId != entityId) {
throw RuntimeException("两个要进行加的 entity id 不一致 $event")
}
if (currentZone == Zone.Deck && newZone == Zone.Deck && newPosition != 0) {
//星界导致插入一张卡牌到事件中
currentZone = event.newZone
newZone = event.newZone
}
if (currentZone != event.currentZone) {
throw RuntimeException("两个要进行加的 zone 不一致 $event")
}
val newPosAndZone =
if (this.currentZone == event.newZone) event.newPosition to newZone else newPosition to event.newZone
return ZonePositionChangedEvent(
id,
gameId,
entityId,
cardId,
cardName,
isUser,
currentZone,
newPosAndZone.second,
currentPosition,
newPosAndZone.first
)
}
fun plusPlus(
second: ZonePositionChangedEvent,
third: ZonePositionChangedEvent
): ZonePositionChangedEvent {
val oldZone = currentZone
val list = listOf(this, second, third)
val newZone = list.map {
it.newZone
}.firstOrNull {
it != oldZone
}
val newPosition = third.newPosition
return ZonePositionChangedEvent(
id,
gameId,
entityId,
cardId,
cardName,
isUser,
currentZone,
newZone ?: this.newZone,
currentPosition,
newPosition
)
}
}
//public operator fun ZonePositionChangedEvent.plus(event: ZonePositionChangedEvent): ZonePositionChangedEvent {
// if (entityId != event.entityId) {
// throw RuntimeException("不支持不同 entityId 的 event 相加")
// }
// if (this == event) {
// return this
// }
//}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/db/ZonePositionChangedEventDao.kt
================================================
package com.ke.hs_tracker.module.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
@Dao
interface ZonePositionChangedEventDao {
@Insert
suspend fun insertAll(list: List<ZonePositionChangedEvent>)
@Query("select * from zone_position_updated_event where game_id = :gameId")
suspend fun getAllByGameId(gameId: String): List<ZonePositionChangedEvent>
/**
* 获取所有
*/
@Query("select * from zone_position_updated_event")
suspend fun getAll(): List<ZonePositionChangedEvent>
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/di/CoroutinesModule.kt
================================================
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ke.hs_tracker.module.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
@InstallIn(SingletonComponent::class)
@Module
object CoroutinesModule {
@DefaultDispatcher
@Provides
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@IoDispatcher
@Provides
fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@MainDispatcher
@Provides
fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
@MainImmediateDispatcher
@Provides
fun providesMainImmediateDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/di/CoroutinesQualifiers.kt
================================================
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ke.hs_tracker.module.di
import javax.inject.Qualifier
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class DefaultDispatcher
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class IoDispatcher
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class MainDispatcher
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class MainImmediateDispatcher
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class ApplicationScope
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/di/LogFileDirQualifiers.kt
================================================
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ke.hs_tracker.module.di
import javax.inject.Qualifier
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class LocalLogFileDir
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class RealLogFileDir
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/di/Module.kt
================================================
package com.ke.hs_tracker.module.di
import android.content.Context
import androidx.room.Room
import com.ke.hs_tracker.module.api.HearthStoneJsonApi
import com.ke.hs_tracker.module.data.PreferenceStorage
import com.ke.hs_tracker.module.data.PreferenceStorageImpl
import com.ke.hs_tracker.module.db.*
import com.ke.hs_tracker.module.entity.*
import com.ke.hs_tracker.module.parser.*
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class Module {
@Provides
@Singleton
fun provideHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.add(CardClassAdapter())
.add(CardTypeAdapter())
.add(RarityAdapter())
.add(SpellSchoolAdapter())
.add(MechanicsAdapter())
.add(RaceAdapter())
.build()
}
@Provides
@Singleton
fun provideHearthStoneJsonApi(
okHttpClient: OkHttpClient,
moshi: Moshi
): HearthStoneJsonApi {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(okHttpClient)
.baseUrl(HearthStoneJsonApi.BASE_URL)
.build()
.create(HearthStoneJsonApi::class.java)
}
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): Database {
return Room.databaseBuilder(
context,
Database::class.java,
"card.db"
).build()
}
@Provides
fun provideCardDao(database: Database): CardDao {
return database.cardDao()
}
@Provides
fun provideGameDao(database: Database): GameDao = database.gameDao()
@Provides
fun provideZonePositionChangedEventDao(database: Database): ZonePositionChangedEventDao =
database.zonePositionChangedEventDao()
@Provides
@Singleton
fun providePreferenceStorage(preferenceStorageImpl: PreferenceStorageImpl): PreferenceStorage {
return preferenceStorageImpl
}
@Provides
fun provideBlockTagStack(impl: BlockTagStackImpl): BlockTagStack {
return impl
}
@Provides
fun providePowerParser(powerParserImpl: PowerParserImpl): PowerParser {
return powerParserImpl
}
@Provides
fun providePowerTagHandler(powerTagHandlerImpl: PowerTagHandlerImpl): PowerTagHandler =
powerTagHandlerImpl
@Provides
fun provideDeckCardObserver(deckCardObserverImpl: DeckCardObserverImpl): DeckCardObserver =
deckCardObserverImpl
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/ClearCardTableUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.db.CardDao
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class ClearCardTableUseCase @Inject constructor(
private val cardDao: CardDao,
@IoDispatcher dispatcher: CoroutineDispatcher
) : UseCase<Unit, Unit>(dispatcher) {
override suspend fun execute(parameters: Unit) {
cardDao.deleteAll()
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/GetAllCardUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.db.CardDao
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.entity.Card
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class GetAllCardUseCase @Inject constructor(
@IoDispatcher dispatcher: CoroutineDispatcher,
private val cardDao: CardDao
) :
UseCase<Unit, List<Card>>(dispatcher) {
override suspend fun execute(parameters: Unit): List<Card> {
return cardDao.getAll()
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/GetCardListUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.api.HearthStoneJsonApi
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.entity.Card
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class GetCardListUseCase @Inject constructor(
@IoDispatcher dispatcher: CoroutineDispatcher,
private val api: HearthStoneJsonApi
) :
UseCase<Pair<String, String>, List<Card>>(dispatcher) {
override suspend fun execute(parameters: Pair<String, String>): List<Card> {
return api.getCardJsonList(
parameters.first,
parameters.second
)
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/GetDatabaseCardCountUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.db.CardDao
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class GetDatabaseCardCountUseCase @Inject constructor(
@IoDispatcher dispatcher: CoroutineDispatcher,
private val cardDao: CardDao
) : UseCase<Unit, Int>(dispatcher) {
override suspend fun execute(parameters: Unit): Int {
return cardDao.getCount()
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/GetLocalLogDirUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import android.content.Context
import androidx.documentfile.provider.DocumentFile
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.findHSDataFilesDir
import com.ke.mvvm.base.domian.UseCase
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import java.io.File
import javax.inject.Inject
class GetLocalLogDirUseCase @Inject constructor(
@IoDispatcher dispatcher: CoroutineDispatcher,
@ApplicationContext private val context: Context
) :
UseCase<Unit, DocumentFile?>(dispatcher) {
override suspend fun execute(parameters: Unit): DocumentFile? {
val logsDir = context.getExternalFilesDir("Logs")!!
if (!logsDir.exists()) {
logsDir.mkdir()
}
// val decksFile = File(logsDir, "Decks.log")
// decksFile.createNewFile()
// context.assets.open("Decks.log").reader()
// .apply {
// decksFile.writeText(readText())
// }
val powerFile = File(logsDir, "Power.log")
powerFile.createNewFile()
return context.findHSDataFilesDir("Logs")
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/GetRealLogDirUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import android.content.Context
import androidx.documentfile.provider.DocumentFile
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.findHSDataFilesDir
import com.ke.hs_tracker.module.log
import com.ke.mvvm.base.domian.UseCase
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class GetRealLogDirUseCase @Inject constructor(
@IoDispatcher dispatcher: CoroutineDispatcher,
@ApplicationContext private val context: Context
) :
UseCase<Unit, DocumentFile?>(dispatcher) {
// private var documentFile: DocumentFile? = null
override suspend fun execute(parameters: Unit): DocumentFile? {
// if (documentFile != null) {
// return documentFile
// }
val logsDir = context.findHSDataFilesDir("Logs")
?: return null
val listFiles = logsDir.listFiles()
return listFiles.filter {
"${it.name} ${it.lastModified()}".log()
(it.name?.startsWith("Hearthstone") ?: false) && it.isDirectory
}.maxByOrNull {
it.lastModified()
}?.apply {
"找到了目标目录 ${this.name} ${this.lastModified()}".log()
}
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/GetSaveLogFileEnableUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.data.PreferenceStorage
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class GetSaveLogFileEnableUseCase @Inject constructor(
private val preferenceStorage: PreferenceStorage,
@IoDispatcher dispatcher: CoroutineDispatcher
) :
UseCase<Unit, Boolean>(dispatcher) {
override suspend fun execute(parameters: Unit): Boolean {
return preferenceStorage.saveLogFile
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/InsertCardListToDatabaseUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.db.CardDao
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.entity.Card
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class InsertCardListToDatabaseUseCase @Inject constructor(
@IoDispatcher dispatcher: CoroutineDispatcher,
private val cardDao: CardDao
) : UseCase<List<Card>, Unit>(dispatcher) {
override suspend fun execute(parameters: List<Card>) {
cardDao.insert(parameters)
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/ParseDeckCodeUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import android.util.Base64
import com.ke.hs_tracker.module.db.CardDao
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.entity.Card
import com.ke.hs_tracker.module.entity.CardBean
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class ParseDeckCodeUseCase
@Inject constructor(
private val cardDao: CardDao,
@IoDispatcher dispatcher: CoroutineDispatcher
) : UseCase<String, List<CardBean>>(dispatcher) {
override suspend fun execute(parameters: String): List<CardBean> {
val allCards = cardDao.getAll()
val byteArray =
Base64.decode(parameters, Base64.DEFAULT)
// deckString.encodeToByteArray().decodeBase64()
//[0, 1, 2, 1, -3, 4, 8, -116, -71, 3, -32, -52, 3, -65, -32, 3, -109, -31, 3, -78, -9, 3, -96, -118, 4, -104, -115, 4, -4, -98, 4, 11, -63, -72, 3, -127, -65, 3, -51, -50, 3, -5, -35, 3, -21, -34, 3, -48, -20, 3, -47, -20, 3, -89, -9, 3, -118, -115, 4, -3, -98, 4, -5, -94, 4, 0]
// val size = byteArray.size
val byteList = mutableListOf<Byte>()
byteArray.forEach {
byteList.add(it)
}
val keep = byteList.removeFirst()//移除第一个保留的字段
// assert(byteList.removeFirst().toInt() == 0)
// assert(byteList.removeFirst().toInt() != 0)//总是1
val version = byteList.removeFirst()
val cardType = byteList.removeFirst()//1是标准 2是狂野
val cardList = mutableListOf<CardBean>()
val heroCount = getVarInt(byteList)
for (i in 0 until heroCount) {
val id = getVarInt(byteList)
val hero =
findByDbfId(id, allCards) ?: throw IllegalArgumentException("找不到id为 $id 的卡牌")
// hero.count = heroCount
cardList.add(hero.updateCount(heroCount))
}
for (i in 1..3) {
val c = getVarInt(byteList)
for (j in 0 until c) {
val dbfId = getVarInt(byteList)
val count: Int
if (i == 3) {
count = getVarInt(byteList)
} else {
count = i
}
// result.cards.add(Card(dbfId, count))
val jsonObject =
findByDbfId(dbfId, allCards)
?: throw IllegalArgumentException("找不到id为 $dbfId 的卡牌")
// jsonObject.count = count
cardList.add(jsonObject.updateCount(count))
}
}
//移除英雄
cardList.removeFirst()
cardList.sortBy {
it.card.cost
}
return cardList
}
/**
* 获得无符号int
*/
private fun getVarInt(src: MutableList<Byte>): Int {
var result = 0
var shift = 0
var b: Int
do {
if (shift >= 32) {
// Out of range
throw IndexOutOfBoundsException("varint too long")
}
// Get 7 bits from next byte
b = src.removeFirst().toInt()
result = result or (b and 0x7F shl shift)
shift += 7
} while (b and 0x80 != 0)
return result
}
private fun findByDbfId(id: Int, cardEntityList: List<Card>): CardBean? {
val cardEntity = cardEntityList.find { it.dbfId == id } ?: return null
return CardBean(cardEntity)
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/SaveLogFileUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import android.content.Context
import android.net.Uri
import com.ke.hs_tracker.module.data.PreferenceStorage
import com.ke.mvvm.base.domian.UseCase
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import java.io.File
import java.io.InputStream
import javax.inject.Inject
class SaveLogFileUseCase @Inject constructor(
private val preferenceStorage: PreferenceStorage,
@ApplicationContext private val context: Context
) :
UseCase<Pair<String, InputStream>, Boolean>(Dispatchers.IO) {
override suspend fun execute(parameters: Pair<String, InputStream>): Boolean {
if (!preferenceStorage.saveLogFile) {
return false
}
val targetFileDir = File(context.getExternalFilesDir(null), "logs")
if (!targetFileDir.exists()) {
targetFileDir.mkdir()
}
val target = File(targetFileDir, parameters.first + ".log")
if (!target.exists()) {
target.createNewFile()
}
val text = parameters.second.bufferedReader().readText()
target.writeText(text)
return true
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/SetSaveLogFileEnableUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import com.ke.hs_tracker.module.data.PreferenceStorage
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.mvvm.base.domian.UseCase
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class SetSaveLogFileEnableUseCase @Inject constructor(
private val preferenceStorage: PreferenceStorage,
@IoDispatcher dispatcher: CoroutineDispatcher
) :
UseCase<Boolean, Boolean>(dispatcher) {
override suspend fun execute(parameters: Boolean): Boolean {
preferenceStorage.saveLogFile = parameters
return parameters
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/domain/WriteLogConfigFileUseCase.kt
================================================
package com.ke.hs_tracker.module.domain
import android.content.Context
import androidx.documentfile.provider.DocumentFile
import com.ke.hs_tracker.module.di.IoDispatcher
import com.ke.hs_tracker.module.findHSDataFilesDir
import com.ke.hs_tracker.module.log
import com.ke.hs_tracker.module.writeLogConfigFile
import com.ke.mvvm.base.domian.UseCase
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import javax.inject.Inject
class WriteLogConfigFileUseCase @Inject constructor(
@ApplicationContext private val context: Context,
@IoDispatcher private val dispatcher: CoroutineDispatcher
) :
UseCase<Boolean, Boolean>(dispatcher) {
override suspend fun execute(parameters: Boolean): Boolean {
return context.writeLogConfigFile(parameters)
// val documentFile = context.findHSDataFilesDir() ?: return false
//
// val fileName = "log.config"
// val file = context.findHSDataFilesDir(fileName)
// if (file != null) {
// //文件已存在
// "log.config文件已存在".log()
//
// if (parameters) {
// file.delete()
// val configFile = documentFile.createFile("plain/text", fileName)
// ?: return false
//
// write(configFile)
// } else {
// return true
// }
// }
// val configFile = documentFile.createFile("plain/text", fileName)
// ?: return false
//
// write(configFile)
//
// return true
}
private fun write(configFile: DocumentFile) {
context.contentResolver.openOutputStream(configFile.uri)?.use {
context.assets.open("log.config")
.copyTo(it)
it.flush()
}
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/BlockType.kt
================================================
package com.ke.hs_tracker.module.entity
enum class BlockType {
/**
* 攻击
*/
Attack,
/**
* 死亡
*/
Deaths,
/**
* 触发
*/
Trigger,
/**
* 打出一张卡牌
*/
Play,
/**
* 卡牌生效
*/
Power,
/**
* 交易
*/
Trade
}
internal fun String.toBlockType(fallback: BlockType = BlockType.Trigger): BlockType {
BlockType.values().forEach {
if (it.name.equals(this, true)) {
return it
}
}
return fallback
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/Card.kt
================================================
package com.ke.hs_tracker.module.entity
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.squareup.moshi.JsonClass
import kotlinx.parcelize.Parcelize
@Entity(tableName = "card")
@JsonClass(generateAdapter = true)
@Parcelize
data class Card(
/**
* 画家
*/
val artist: String? = null,
/**
* 名称
*/
val name: String,
/**
* 费用
*/
val cost: Int = 0,
@PrimaryKey
val id: String,
val dbfId: Int,
val text: String = "",
//属于哪个版本 例如 TGT
val set: String = "",
val type: CardType = CardType.None,
val cardClass: CardClass? = null,
val classes: List<CardClass> = emptyList(),
val mechanics: List<Mechanics> = emptyList(),
/**
* 个性介绍
*/
val flavor: String = "",
val rarity: Rarity? = null,
/**
* 武器耐久
*/
val durability: Int = 0,
/**
* 英雄牌的护甲
*/
val armor: Int = 0,
val collectible: Boolean = false,
/**
* 法术类型
*/
val spellSchool: SpellSchool? = null,
/**
* 随从种族
*/
val race: Race? = null,
/**
* 随从攻击力
*/
val attack: Int = 0,
/**
* 随从生命值
*/
val health: Int = 0,
) : Parcelable
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/CardBean.kt
================================================
package com.ke.hs_tracker.module.entity
data class CardBean(
val card: Card,
val count: Int = 0
) {
//防止在使用MutableStateFlow时无法更新数据
// override fun equals(other: Any?): Boolean {
// return false
// }
//
// override fun hashCode(): Int {
// return Random.nextInt()
// }
fun updateCount(count: Int): CardBean {
return CardBean(card, count)
}
fun toCardList(): List<Card> {
val list = mutableListOf<Card>()
repeat(count) {
list.add(card)
}
return list
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/CardClass.kt
================================================
package com.ke.hs_tracker.module.entity
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.ke.hs_tracker.module.R
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
enum class CardClass(
@StringRes
val titleRes: Int,
@ColorRes
val color: Int = 0,
@DrawableRes
val roundIcon: Int? = null,
val isHero: Boolean = true
) {
/**
* 法师
*/
Mage(R.string.module_mage, R.color.module_mage, R.drawable.module_image_round_mage),
/**
* 术士
*/
Warlock(R.string.module_warlock, R.color.module_warlock, R.drawable.module_image_round_warlock),
/**
* 牧师
*/
Priest(R.string.module_priest, R.color.module_priest, R.drawable.module_image_round_priest),
/**
* 德鲁伊
*/
Druid(R.string.module_druid, R.color.module_druid, R.drawable.module_image_round_druid),
/**
* 盗贼
*/
Rogue(R.string.module_rogue, R.color.module_rogue, R.drawable.module_image_round_rogue),
/**
* 萨满
*/
Shaman(R.string.module_shaman, R.color.module_shaman, R.drawable.module_image_round_shaman),
/**
* 猎人
*/
Hunter(R.string.module_hunter, R.color.module_hunter, R.drawable.module_image_round_hunter),
/**
* 圣骑士
*/
Paladin(R.string.module_paladin, R.color.module_paladin, R.drawable.module_image_round_paladin),
/**
* 战士
*/
Warrior(R.string.module_warrior, R.color.module_warrior, R.drawable.module_image_round_warrior),
/**
* 恶魔猎手
*/
DemonHunter(
R.string.module_demon_hunter,
R.color.module_demon_hunter,
R.drawable.module_image_round_demon_hunter
),
/**
* 中立
*/
Neutral(R.string.module_neutral, R.color.module_neutral, isHero = false),
/**
* 威兹班
*/
Whizbang(R.string.module_whizbang, 0, isHero = false),
/**
* 梦境牌
*/
Dream(R.string.module_dream, 0, isHero = false),
/**
* 死亡骑士
*/
DeathKnight(R.string.module_death_knight, R.color.module_death_knight, isHero = false),
}
class CardClassAdapter {
@FromJson
fun fromJson(value: String): CardClass {
return EnumMoshiAdapter.fromJson(value, CardClass.values())
}
@ToJson
fun toJson(cardClass: CardClass) = cardClass.name.uppercase()
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/CardType.kt
================================================
package com.ke.hs_tracker.module.entity
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
enum class CardType {
/**
* 英雄
*/
Hero,
/**
* 英雄技能
*/
HeroPower,
/**
*衍生牌
*/
Enchantment,
/**
* 法术
*/
Spell,
/**
* 随从
*/
Minion,
/**
* 武器
*/
Weapon,
None
}
class CardTypeAdapter {
@FromJson
fun fromJson(value: String): CardType {
return CardType.values()
.find { it.name.equals(value.replace("_", ""), true) } ?: CardType.None
}
@ToJson
fun toJson(cardType: CardType) = cardType.name.uppercase()
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/CurrentDeck.kt
================================================
package com.ke.hs_tracker.module.entity
data class CurrentDeck(
val name: String,
val code: String
)
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/Entity.kt
================================================
package com.ke.hs_tracker.module.entity
import com.ke.hs_tracker.module.parser.PowerParserImpl
//有三种形式
//1,Entity=[entityName=腐食研习 id=29 zone=PLAY zonePos=0 cardId=SCH_300 player=1]
//2,Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=29 zone=HAND zonePos=3 cardId= player=1]
//3,Entity=失落的裤子#5629
data class Entity(
val entityName: String,
val gameCardType: GameCardType? = null,
val id: Int = -1,
val zone: Zone = Zone.Play,
val zonePosition: Int = -1,
val cardId: String? = null,
val player: Int = -1
) {
val entityType: EntityType
get() = when {
gameCardType == null && id == -1 && zone == Zone.Play && zonePosition == -1 && cardId == null && player == -1 -> EntityType.Name
gameCardType == GameCardType.Invalid -> EntityType.Invalid
else -> EntityType.Clear
}
val isGameEntity: Boolean
get() = entityName == "GameEntity"
val isUserEntity: Boolean
get() = entityName.contains("#")
companion object {
internal fun createFromContent(content: String): Entity? {
if (content == "0") {
return null
}
var matchResult = PowerParserImpl.FULL_ENTITY_CONTENT1_PATTERN.matchEntire(content)
if (matchResult != null) {
return Entity(
matchResult.groupValues[1],
matchResult.groupValues[2].toCardType(GameCardType.Invalid),
matchResult.groupValues[3].toIntOrNull() ?: 0,
matchResult.groupValues[4].toZone(),
matchResult.groupValues[5].toIntOrNull() ?: 0,
matchResult.groupValues[6].ifBlank { null },
matchResult.groupValues[7].toIntOrNull() ?: 0
)
}
matchResult = PowerParserImpl.FULL_ENTITY_CONTENT2_PATTERN.matchEntire(content)
?: return Entity(content)
return Entity(
matchResult.groupValues[1],
GameCardType.Invalid,
matchResult.groupValues[2].toIntOrNull() ?: 0,
matchResult.groupValues[3].toZone(),
matchResult.groupValues[4].toIntOrNull() ?: 0,
matchResult.groupValues[5].ifBlank { null },
matchResult.groupValues[6].toIntOrNull() ?: 0
)
}
}
}
enum class EntityType {
//Entity=失落的裤子#5629
Name,
//Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=29 zone=HAND zonePos=3 cardId= player=1]
Invalid,
//Entity=[entityName=腐食研习 id=29 zone=PLAY zonePos=0 cardId=SCH_300 player=1]
Clear
}
data class GameEntity(
val gameCardType: GameCardType,
val entityId: Int
)
/**
* 玩家
*/
data class Player(
val entityId: Int,
val playerId: Int,
val controller: Int,
val gameCardType: GameCardType,
val heroEntity: Int,
/**
* 手牌上限
*/
val maxHandSize: Int,
/**
* 起始手牌
*/
val startHandSize: Int,
val teamId: Int,
/**
* 费用上限 一般为10
*/
val maxResources: Int
) {
companion object {
internal fun fromMap(map: Map<String, String>): Player {
return Player(
entityId = map["entityid"]?.toIntOrNull() ?: 0,
playerId = map["playerid"]?.toIntOrNull() ?: 0,
controller = map["controller"]?.toIntOrNull() ?: 0,
gameCardType = (map["cardtype"] ?: "").toCardType(),
heroEntity = map["heroentity"]?.toIntOrNull() ?: 0,
maxHandSize = map["maxhandsize"]?.toIntOrNull() ?: 0,
startHandSize = map["starthandsize"]?.toIntOrNull() ?: 0,
teamId = map["teamid"]?.toIntOrNull() ?: 0,
maxResources = map["maxresources"]?.toIntOrNull() ?: 0
)
}
}
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/EntityWithPayload.kt
================================================
package com.ke.hs_tracker.module.entity
data class EntityWithPayload(
val entity: Entity,
private val payloads: MutableMap<String, String> = mutableMapOf()
) {
fun add(pair: Pair<String, String>) {
payloads[pair.first] = pair.second
}
fun getPayloads(): Map<String, String> = payloads
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/EnumMoshiAdapter.kt
================================================
package com.ke.hs_tracker.module.entity
object EnumMoshiAdapter {
fun <T : Enum<T>> fromJson(value: String, enumList: Array<T>, fallback: T? = null): T {
val enum = enumList
.find { it.name.equals(value.replace("_", ""), true) } ?: fallback
if (enum == null) {
throw IllegalArgumentException("错误的 $value 类型")
}
return enum
}
fun <T : Enum<T>> toJson(value: T) = value.name.uppercase()
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/FormatType.kt
================================================
package com.ke.hs_tracker.module.entity
import androidx.annotation.StringRes
import com.ke.hs_tracker.module.R
enum class FormatType(@StringRes val title: Int) {
Unknown(R.string.module_unknown),
/**
* 标准
*/
Standard(R.string.module_standard),
/**
* 狂野
*/
Wild(R.string.module_wild),
/**
* 经典
*/
Classic(R.string.module_classic)
}
val String.toFormatType: FormatType
get() = FormatType.values().find {
this.contains(it.name, true)
} ?: FormatType.Unknown
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/GameCardType.kt
================================================
package com.ke.hs_tracker.module.entity
/**
* 卡牌类型
*/
enum class GameCardType {
Game,
/**
* 玩家
*/
Player,
/**
* 英雄
*/
Hero,
/**
* 英雄技能
*/
HeroPower,
/**
* 牌库中的牌的状态
*/
Invalid,
/**
* 随从身上的buff或战场上的buff(例如下一张法强怪法力值减少1)
*/
Enchantment,
/**
* 法术
*/
Spell,
/**
* 随从
*/
Minion
}
/**
* 字符串转 CardType类型
*/
internal fun String.toCardType(fallback: GameCardType = GameCardType.Game): GameCardType {
return GameCardType.values().find {
it.name.equals(this.replace("_", ""), true)
} ?: fallback
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/GameEvent.kt
================================================
package com.ke.hs_tracker.module.entity
import com.ke.hs_tracker.module.db.Game
/**
* 游戏事件
*/
sealed interface GameEvent {
/**
* 游戏开始
*/
object OnGameStart : GameEvent
/**
* 游戏结束
*/
data class OnGameOver(
val game: Game
) : GameEvent
/**
* 插入一张卡牌到用户的牌库
*/
data class InsertCardToUserDeck(
val cardId: String
) : GameEvent
/**
* 从牌库中移除一张卡牌
*/
data class RemoveCardFromUserDeck(
val cardId: String
) : GameEvent
/**
* 插入一张卡牌到墓地
*/
data class InsertCardToGraveyard(val cardId: String, val isUser: Boolean) : GameEvent
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/GameType.kt
================================================
package com.ke.hs_tracker.module.entity
import androidx.annotation.StringRes
import com.ke.hs_tracker.module.R
enum class GameType(@StringRes val title: Int) {
/**
* 排名
*/
Ranked(R.string.module_ranked),
/**
* 休闲
*/
Casual(R.string.module_casual),
Unknown(R.string.module_unknown)
}
val String.toGameType: GameType
get() = GameType.values().find {
this.contains(it.name, true)
} ?: GameType.Unknown
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/GraveyardCard.kt
================================================
package com.ke.hs_tracker.module.entity
data class GraveyardCard(
val card: Card,
/**
* 插入时间
*/
val time: Long = System.currentTimeMillis()
)
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/InsertStackResult.kt
================================================
package com.ke.hs_tracker.module.entity
sealed interface InsertStackResult {
/**
* 插入成功
*/
object Success : InsertStackResult
/**
* 不能插入,例如不在Block内的TAG_CHANGE
*/
object CanNotInsert : InsertStackResult
/**
* 结束了
* @param powerTag tag
* @param handled 是否处理了本次log日志,例如 FULL_ENTITY - Updating 跟着一个 FULL_ENTITY - Updating的情况,handled就是true,表示已经处理了,不需要在进行处理;如果是FULL_ENTITY - Updating
* 跟着一个 TAG_CHANGE,就表示没有处理,需要调用这个方法的自行处理log数据
*/
data class Over(val powerTag: PowerTag, val handled: Boolean) : InsertStackResult
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/LogType.kt
================================================
package com.ke.hs_tracker.module.entity
enum class LogType(val replace: String) {
PowerTaskList("PowerTaskList.DebugPrintPower() -"),
GameState("GameState.DebugPrintGame() -")
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/Mechanics.kt
================================================
package com.ke.hs_tracker.module.entity
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
/**
* 类型
*/
enum class Mechanics {
/**
* 注能
*/
Infuse,
/**
* 过载
*/
Overload,
/**
* 战吼
*/
BattleCry,
/**
* 光环
*/
Aura,
/**
* 冲锋
*/
Charge,
/**
* 嘲讽
*/
Taunt,
/**
* 突袭
*/
Rush,
/**
* 巨型
*/
Colossal,
/**
* 探底
*/
Dredge,
/**
* 潜行
*/
Stealth,
/**
* 圣盾
*/
DivineShield,
/**
* 法强
*/
SpellPower,
/**
* 抽到的时候
*/
TopDeck,
/**
* 亡语
*/
DeathRattle,
/**
* 秘密选择
*/
Counter,
InvisibleDeathRattle,
/**
* 变形
*/
Morph,
/**
* 激怒
*/
Enraged,
/**
* 50%几率攻击错误的敌人
*/
Forgetful,
/**
* 本回合生效
*/
TagOneTurnEffect,
/**
* 抉择
*/
ChooseOne,
/**
* 荣誉击杀
*/
HonorableKill,
/**
* 法力迸发
*/
SpellBurst,
/**
* 腐蚀
*/
Corrupt,
/**
* 暴怒
*/
Frenzy,
/**
* 发现
*/
Discover,
/**
* 冻结
*/
Freeze,
/**
* 连击
*/
Combo,
/**
* 无法攻击
*/
CantAttack,
/**
* 触发
*/
TriggerVisual,
/**
* 奥秘
*/
Secret,
/**
* 任务
*/
Quest,
/**
* 克苏恩
*/
Ritual,
/**
* 交易
*/
Tradeable,
/**
* 激励
*/
Inspire,
/**
* 沉默
*/
Silence,
/**
* 风怒
*/
Windfury,
/**
* 回响
*/
Echo,
/**
* 污手党
*/
GrimyGoons,
/**
* 暗金教
*/
Kabal,
/**
* 玉莲帮
*/
JadeLotus,
/**
* 青玉魔像
*/
JadeGolem,
/**
* 不可见的衍生牌
*/
EnchantmentInvisible,
/**
* 英雄技能造成额外的伤害
*/
HeroPowerDamage,
/**
* 回合结束时如果这张牌仍在手牌中,将其摧毁
*/
Ghostly,
/**
* 受到法强翻倍
*/
ReceivesDoubleSpellDamageBonus,
/**
* 零件
*/
SparePart,
AutoAttack,
/**
* 唤尸者专属
*/
DeathKnight,
/**
* 偶数
*/
CollectionmanagerFilterManaEven,
/**
* 奇数
*/
CollectionmanagerFilterManaOdd,
/**
* 不受法强影响的法术
*/
ImmuneToSpellPower,
/**
* 无法被沉默
*/
CantBeSilenced,
CantBeDestroyed,
/**
* 黑棋国王
*/
CantBeFatigued,
/**
* 支线任务
*/
SideQuest,
/**
* 双生法术
*/
TwinSpell,
/**
* 剧毒
*/
Poisonous,
/**
* 吸血
*/
LifeSteal,
/**
* 流放
*/
Outcast,
/**
* 不可接触的
*/
Untouchable,
/**
* 复仇
*/
Avenge,
/**
* 超杀
*/
Overkill,
/**
* 复生
*/
Reborn,
AIMustPlay,
/**
* 免疫
*/
Immune,
/**
* 无法成为法术的目标
*/
CantBeTargetedBySpells,
/**
* 无法成为英雄技能的目标
*/
CantBeTargetedByHeroPowers,
/**
* 磁力
*/
Modular,
/**
* 如果这张牌在你的手牌中,在你的回合开始时,你的英雄受到2点伤害
*/
EvilGlow,
/**
* 相邻的随从获得buff
*/
AdjacentBuff,
/**
* 对局开始时
*/
StartOfGame,
/**
* 在你攻击一个随从后,迫使其攻击相邻的一个随从
*/
FinishAttackSpellOnDamage,
AppearFunctionallyDead,
Gears,
Puzzle,
MultiplyBuffValue,
AffectedBySpellPower,
DungeonPassiveBuff,
IgnoreHideStatsForBigCard,
Summoned,
/**
* 法力渴求
*/
ManaThirst,
OVERHEAL,
VENOMOUS,
MAGNETIC,
FORGE,
TITAN
}
class MechanicsAdapter {
@FromJson
fun fromJson(value: String): Mechanics {
return EnumMoshiAdapter.fromJson(value, Mechanics.values())
}
@ToJson
fun toJson(mechanics: Mechanics) = EnumMoshiAdapter.toJson(mechanics)
}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/NestedTag.kt
================================================
package com.ke.hs_tracker.module.entity
internal sealed interface NestedTag {
object CreateGame : NestedTag
data class GameEntity(val id: Int) : NestedTag
data class Tag(val key: String, val value: String) : NestedTag {
fun toPair(): Pair<String, String> = key to value
}
data class Player(val entityId: Int, val playerId: Int) : NestedTag
//FULL_ENTITY - Updating [entityName=UNKNOWN ENTITY [cardType=INVALID] id=63 zone=DECK zonePos=0 cardId= player=2] CardID=
//FULL_ENTITY - Updating [entityName=全副武装! id=65 zone=PLAY zonePos=0 cardId=HERO_01bp player=1] CardID=HERO_01bp
data class FullEntity(
val entity: Entity,
val cardId: String?
) : NestedTag
data class Block(
val blockType: BlockType,
val entity: Entity,
val target: Entity?
) : NestedTag
data class TagChange(
val entity: Entity,
val tag: String,
val value: String
) : NestedTag {
fun convert(): PowerTag.PowerTaskList.TagChange {
return PowerTag.PowerTaskList.TagChange(entity, tag, value)
}
}
data class ShowEntity(
val entity: Entity,
val cardId: String
) : NestedTag
object BlockEnd : NestedTag
}
//internal fun NestedTag.FullEntity.toUpdating(): PowerTag.PowerTaskList.FullEntity.Updating {
// return PowerTag.PowerTaskList.FullEntity.Updating(
// entityName, cardType, id, zone, zonePosition, cardId, player
// )
//}
================================================
FILE: module/src/main/java/com/ke/hs_tracker/module/entity/PowerTag.kt
================================================
package com.ke.hs_tracker.module.entity
import com.ke.hs_tracker.module.db.ZonePositionChangedEvent
sealed interface PowerTag {
sealed interface PowerTaskList : PowerTag {
/**
* 创建游戏
*/
//D 19:55:18.1257030 GameState.DebugPrintPower() - CREATE_GAME
//D 19:55:18.1257030 GameState.DebugPrintPower() - GameEntity EntityID=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CARDTYPE value=GAME
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ZONE value=PLAY
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ENTITY_ID value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=937 value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=SPAWN_TIME_COUNT value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=GAME_SEED value=1950487951
//D 19:55:18.1257030 GameState.DebugPrintPower() - Player EntityID=2 PlayerID=1 GameAccountId=[hi=144115211015832391 lo=191215280]
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CONTROLLER value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CARDTYPE value=PLAYER
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=PLAYER_ID value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=HERO_ENTITY value=64
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXHANDSIZE value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=STARTHANDSIZE value=4
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=TEAM_ID value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ZONE value=PLAY
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ENTITY_ID value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXRESOURCES value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=SPAWN_TIME_COUNT value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=AVRANK value=336
//D 19:55:18.1257030 GameState.DebugPrintPower() - Player EntityID=3 PlayerID=2 GameAccountId=[hi=144115211015832391 lo=44511141]
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CONTROLLER value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=CARDTYPE value=PLAYER
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=PLAYER_ID value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=HERO_ENTITY value=66
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXHANDSIZE value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=STARTHANDSIZE value=4
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=TEAM_ID value=2
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ZONE value=PLAY
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=ENTITY_ID value=3
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=MAXRESOURCES value=10
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=SPAWN_TIME_COUNT value=1
//D 19:55:18.1257030 GameState.DebugPrintPower() - tag=AVRANK value=338
data class CreateGame(
val gameEntity: GameEntity,
val player1: Player,
val player2: Player
) : PowerTaskList
data class TagChange(
override val entity: Entity,
val tag: String,
val value: String,
) : PowerTaskList, ZoneUpdatable {
/**
* 是否是游戏完成的标志
*/
val isGameComplete: Boolean =
entity.entityName == "GameEntity" && tag.equals("state", true) && value.equals(
"COMPLETE",
true
)
/**
* 是否是玩家胜利或失败
*/
val isPlayerWonOrLost: Pair<String, Boolean>?
get() {
return if (tag == "PLAYSTATE" && value == "WON") {
entity.entityName to true
} else if (tag == "PLAYSTATE" && value == "LOST") {
entity.entityName to false
} else {
null
}
}
//TAG_CHANGE Entity=GameEntity tag=TURN value=1
fun isTurnChanged(): Int? {
if (entity.isGameEntity && tag == "TURN") {
return value.toIntOrNull()
}
return null
}
//TAG_CHANGE Entity=GameEntity tag=NUM_TURNS_IN_PLAY value=5
fun isNumTurnsInPlayChanged(): Int? {
if (entity.isGameEntity && tag == "NUM_TURNS_IN_PLAY") {
return value.toIntOrNull()
}
return null
}
override fun getZoneString(): String? {
return if (tag == "ZONE") value else null
}
override fun getZonePositionString(): String? {
return if (tag == "ZONE_POSITION") value else null
}
}
data class FullEntity(
override val entity: Entity,
val payloads: MutableMap<String, String> = mutableMapOf(),
val cardId: String?
) : PowerTaskList, ZoneUpdatable {
fun append(value: Pair<String, String>) {
payloads[value.first] = value.second
}
/**
* 是否是更新英雄置于战场
*/
fun isUpdateHero(): Pair<Int, String?>? {
if (entity.zone == Zone.Play && payloads.count {
it.key == "CARDTYPE" && it.value == "HERO"
} == 1) {
return entity.player to entity.cardId
}
return null
}
//FULL_ENTITY - Updating [entityName=UNKNOWN ENTITY [cardType=INVALID] id=50 zone=DECK zonePos=0 cardId= player=2] CardID=
// tag=ZONE value=DECK
// tag=CONTROLLER value=2
// tag=ENTITY_ID value=50
override fun getZoneString(): String? {
return payloads["ZONE"]
}
override fun getZonePositionString(): String? {
return payloads["ZONE_POSITION"]
}
override fun convertEntity(entity: Entity): Entity {
entity.run {
return Entity(
entityName,
gameCardType,
id,
zone,
zonePosition,
this@FullEntity.cardId,
player
)
}
}
//起始发牌
//FULL_ENTITY - Updating [entityName=UNKNOWN ENTITY [cardType=INVALID] id=49 zone=DECK zonePos=0 cardId= player=2] CardID=
// tag=ZONE value=DECK
// tag=CONTROLLER value=2
// tag=ENTITY_ID value=49
//发现一张牌
//FULL_ENTITY - Updating [entityName=UNKNOWN ENTITY [cardType=INVALID] id=113 zone=SETASIDE zonePos=0 cardId= player=2] CardID=
// tag=ZONE value=SETASIDE
// tag=CONTROLLER value=2
// tag=ENTITY_ID value=113
// override fun shouldIgnoreSameZone(): Boolean {
// return true
// }
}
data class ShowEntity(
override val entity: Entity,
val cardId: String,
val payloads: MutableMap<String, String> = mutableMapOf()
) : PowerTaskList, ZoneUpdatable {
fun append(value: Pair<String, String>) {
payloads[value.first] = value.second
}
fun entityWithCardId(): Entity {
return entity.run {
Entity(
entityName,
gameCardType,
id,
zone,
zonePosition,
this@ShowEntity.cardId,
player
)
}
}
override fun getZoneString(): String? {
return payloads["ZONE"]
}
override fun getZonePositionString(): String? {
return payloads["ZONE_POSITION"]
}
override fun convertEntity(entity: Entity): Entity {
entity.run {
return Entity(
entityName,
gameCardType,
id,
zone,
zonePosition,
this@ShowEntity.cardId,
player
)
}
}
// override fun isUpdated(): Pair<Entity, Zone>? {
// val newZoneString = payloads["ZONE"] ?: return null
// val newZone = newZoneString.toZone(Zone.Unknown)
// if (newZone == Zone.Unknown) {
// throw RuntimeException("错误的zone $newZoneString")
// }
// if (entity.zone == newZone) {
// return null
// }
// return entity to newZone
// }
}
data class Block(
val type: BlockType,
val entity: Entity,
val target: Entity?,
val list: List<PowerTag>
) : PowerTaskList {
/**
* 是否是第一回合
*/
fun ifFirstTurn(): Boolean {
return type == BlockType.Trigger && entity.isGameEntity && list.mapNotNull {
gitextract_40etbzh8/
├── .gitignore
├── 123456
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── release/
│ │ ├── app-1-3-1.apk
│ │ └── output-metadata.json
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── ke/
│ └── hs_tracker/
│ └── app/
│ ├── App.kt
│ └── MainActivity.kt
├── build.gradle
├── core/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs_tracker/
│ └── core/
│ ├── api/
│ │ └── HearthStoneJsonApi.kt
│ ├── entity/
│ │ ├── BlockType.kt
│ │ ├── Card.kt
│ │ ├── CardClass.kt
│ │ ├── CurrentDeck.kt
│ │ ├── Entity.kt
│ │ ├── GameCardType.kt
│ │ ├── InsertStackResult.kt
│ │ ├── LogType.kt
│ │ ├── Mechanics.kt
│ │ ├── NestedTag.kt
│ │ ├── PowerTag.kt
│ │ └── Zone.kt
│ ├── extensions.kt
│ └── parser/
│ ├── BlockTagStack.kt
│ ├── DeckFileObserver.kt
│ ├── PowerFileObserver.kt
│ └── PowerParser.kt
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── libs.versions.toml
├── module/
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ ├── schemas/
│ │ └── com.ke.hs_tracker.module.db.Database/
│ │ ├── 1.json
│ │ ├── 2.json
│ │ ├── 3.json
│ │ └── 4.json
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── ke/
│ │ └── hs_tracker/
│ │ └── module/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/
│ │ │ ├── Decks.log
│ │ │ ├── Power.log
│ │ │ └── log.config
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── ke/
│ │ │ └── hs_tracker/
│ │ │ └── module/
│ │ │ ├── MainApplication.kt
│ │ │ ├── api/
│ │ │ │ └── HearthStoneJsonApi.kt
│ │ │ ├── data/
│ │ │ │ └── PreferenceStorage.kt
│ │ │ ├── db/
│ │ │ │ ├── CardClassesConvert.kt
│ │ │ │ ├── CardDao.kt
│ │ │ │ ├── Database.kt
│ │ │ │ ├── Game.kt
│ │ │ │ ├── GameDao.kt
│ │ │ │ ├── MechanicsListConvert.kt
│ │ │ │ ├── ZonePositionChangedEvent.kt
│ │ │ │ └── ZonePositionChangedEventDao.kt
│ │ │ ├── di/
│ │ │ │ ├── CoroutinesModule.kt
│ │ │ │ ├── CoroutinesQualifiers.kt
│ │ │ │ ├── LogFileDirQualifiers.kt
│ │ │ │ └── Module.kt
│ │ │ ├── domain/
│ │ │ │ ├── ClearCardTableUseCase.kt
│ │ │ │ ├── GetAllCardUseCase.kt
│ │ │ │ ├── GetCardListUseCase.kt
│ │ │ │ ├── GetDatabaseCardCountUseCase.kt
│ │ │ │ ├── GetLocalLogDirUseCase.kt
│ │ │ │ ├── GetRealLogDirUseCase.kt
│ │ │ │ ├── GetSaveLogFileEnableUseCase.kt
│ │ │ │ ├── InsertCardListToDatabaseUseCase.kt
│ │ │ │ ├── ParseDeckCodeUseCase.kt
│ │ │ │ ├── SaveLogFileUseCase.kt
│ │ │ │ ├── SetSaveLogFileEnableUseCase.kt
│ │ │ │ └── WriteLogConfigFileUseCase.kt
│ │ │ ├── entity/
│ │ │ │ ├── BlockType.kt
│ │ │ │ ├── Card.kt
│ │ │ │ ├── CardBean.kt
│ │ │ │ ├── CardClass.kt
│ │ │ │ ├── CardType.kt
│ │ │ │ ├── CurrentDeck.kt
│ │ │ │ ├── Entity.kt
│ │ │ │ ├── EntityWithPayload.kt
│ │ │ │ ├── EnumMoshiAdapter.kt
│ │ │ │ ├── FormatType.kt
│ │ │ │ ├── GameCardType.kt
│ │ │ │ ├── GameEvent.kt
│ │ │ │ ├── GameType.kt
│ │ │ │ ├── GraveyardCard.kt
│ │ │ │ ├── InsertStackResult.kt
│ │ │ │ ├── LogType.kt
│ │ │ │ ├── Mechanics.kt
│ │ │ │ ├── NestedTag.kt
│ │ │ │ ├── PowerTag.kt
│ │ │ │ ├── Race.kt
│ │ │ │ ├── Rarity.kt
│ │ │ │ ├── SpellSchool.kt
│ │ │ │ ├── Turn.kt
│ │ │ │ ├── Zone.kt
│ │ │ │ └── ZoneCard.kt
│ │ │ ├── parser/
│ │ │ │ ├── BlockTagStack.kt
│ │ │ │ ├── DeckCardObserver.kt
│ │ │ │ ├── DeckFileObserver.kt
│ │ │ │ ├── PowerFileObserver.kt
│ │ │ │ ├── PowerParser.kt
│ │ │ │ └── PowerTagHandler.kt
│ │ │ ├── service/
│ │ │ │ ├── ItemViewTouchListener.kt
│ │ │ │ ├── ScaleTouchListener.kt
│ │ │ │ └── WindowService.kt
│ │ │ └── ui/
│ │ │ ├── chart/
│ │ │ │ ├── GetSummaryChartViewDataUseCase.kt
│ │ │ │ ├── PieChartData.kt
│ │ │ │ ├── SummaryChartActivity.kt
│ │ │ │ └── SummaryChartViewData.kt
│ │ │ ├── classbattledetail/
│ │ │ │ ├── ClassBattleDetailActivity.kt
│ │ │ │ ├── ClassBattleDetailViewModel.kt
│ │ │ │ ├── ClassBattleItem.kt
│ │ │ │ └── GetClassBattleItemListUseCase.kt
│ │ │ ├── common/
│ │ │ │ ├── CardAdapter.kt
│ │ │ │ └── LoadingFragment.kt
│ │ │ ├── deck/
│ │ │ │ ├── DeckCodeParserActivity.kt
│ │ │ │ └── DeckCodeParserViewModel.kt
│ │ │ ├── deckbattledetail/
│ │ │ │ ├── BattleRecordsFragment.kt
│ │ │ │ ├── BattleRecordsViewModel.kt
│ │ │ │ ├── DeckBattleDetailActivity.kt
│ │ │ │ ├── DeckDetailFragment.kt
│ │ │ │ ├── DeckDetailViewModel.kt
│ │ │ │ ├── DeckFragment.kt
│ │ │ │ ├── DeckViewModel.kt
│ │ │ │ ├── GetGamesByDeckCodeAndNameUseCase.kt
│ │ │ │ ├── SummaryFragment.kt
│ │ │ │ └── SummaryViewModel.kt
│ │ │ ├── diagnose/
│ │ │ │ └── DiagnoseActivity.kt
│ │ │ ├── filter/
│ │ │ │ └── FilterActivity.kt
│ │ │ ├── main/
│ │ │ │ ├── CardListFragment.kt
│ │ │ │ ├── DeckCardListFragment.kt
│ │ │ │ ├── GraveyardFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainViewModel.kt
│ │ │ │ ├── OpponentGraveyardFragment.kt
│ │ │ │ ├── OpponentHandCardsFragment.kt
│ │ │ │ └── UserGraveyardFragment.kt
│ │ │ ├── migrate/
│ │ │ │ ├── MigrateDataConvert.kt
│ │ │ │ ├── MigrateMainActivity.kt
│ │ │ │ ├── SocketClientActivity.kt
│ │ │ │ └── SocketServerActivity.kt
│ │ │ ├── permissions/
│ │ │ │ ├── PermissionsActivity.kt
│ │ │ │ └── PermissionsViewModel.kt
│ │ │ ├── records/
│ │ │ │ ├── RecordAdapter.kt
│ │ │ │ ├── RecordsActivity.kt
│ │ │ │ └── RecordsViewModel.kt
│ │ │ ├── settings/
│ │ │ │ ├── SettingsActivity.kt
│ │ │ │ └── SettingsViewModel.kt
│ │ │ ├── splash/
│ │ │ │ ├── SplashActivity.kt
│ │ │ │ └── SplashViewModel.kt
│ │ │ ├── summary/
│ │ │ │ ├── BattleRateItem.kt
│ │ │ │ ├── BattleRateItemAdapter.kt
│ │ │ │ ├── BattleRateListFragment.kt
│ │ │ │ ├── BattleRateListViewModel.kt
│ │ │ │ ├── DeckBattleRateListViewModel.kt
│ │ │ │ ├── GetDeckBattleRateListUseCase.kt
│ │ │ │ ├── GetHeroBattleRateListUseCase.kt
│ │ │ │ ├── HeroBattleRateListViewModel.kt
│ │ │ │ ├── RateByDeckFragment.kt
│ │ │ │ ├── RateByHeroFragment.kt
│ │ │ │ └── SummaryActivity.kt
│ │ │ ├── support/
│ │ │ │ └── SupportActivity.kt
│ │ │ ├── sync/
│ │ │ │ ├── SyncCardDataActivity.kt
│ │ │ │ └── SyncCardDataViewModel.kt
│ │ │ ├── test/
│ │ │ │ ├── CreateRecordActivity.kt
│ │ │ │ ├── LocalFileParserActivity.kt
│ │ │ │ └── TestActivity.kt
│ │ │ ├── theme/
│ │ │ │ └── ThemeActivity.kt
│ │ │ ├── writeconfig/
│ │ │ │ └── WriteConfigActivity.kt
│ │ │ ├── zonecards/
│ │ │ │ └── ZoneCardsActivity.kt
│ │ │ └── zoneevents/
│ │ │ ├── ListModeFragment.kt
│ │ │ ├── ZoneEventsActivity.kt
│ │ │ └── ZoneEventsViewModel.kt
│ │ └── res/
│ │ ├── color/
│ │ │ └── module_game_state.xml
│ │ ├── drawable/
│ │ │ ├── module_baseline_arrow_back_white_24dp.xml
│ │ │ ├── module_baseline_clear_black_24dp.xml
│ │ │ ├── module_baseline_clear_red_500_24dp.xml
│ │ │ ├── module_baseline_done_black_24dp.xml
│ │ │ ├── module_baseline_done_green_500_24dp.xml
│ │ │ ├── module_baseline_done_white_24dp.xml
│ │ │ ├── module_baseline_drag_handle_white_24dp.xml
│ │ │ ├── module_baseline_keyboard_arrow_right_grey_500_24dp.xml
│ │ │ ├── module_baseline_pie_chart_white_24dp.xml
│ │ │ ├── module_baseline_play_arrow_white_24dp.xml
│ │ │ ├── module_baseline_settings_white_24dp.xml
│ │ │ ├── module_baseline_sync_white_24dp.xml
│ │ │ └── module_baseline_zoom_out_map_white_24dp.xml
│ │ ├── drawable-xxxhdpi/
│ │ │ └── module_bg_splash.xml
│ │ ├── layout/
│ │ │ ├── module_activity_class_battle_detail.xml
│ │ │ ├── module_activity_create_record.xml
│ │ │ ├── module_activity_deck_battle_detail.xml
│ │ │ ├── module_activity_deck_code_parser.xml
│ │ │ ├── module_activity_diagnose.xml
│ │ │ ├── module_activity_filter.xml
│ │ │ ├── module_activity_local_file_parser.xml
│ │ │ ├── module_activity_main.xml
│ │ │ ├── module_activity_migrate_main.xml
│ │ │ ├── module_activity_permissions.xml
│ │ │ ├── module_activity_records.xml
│ │ │ ├── module_activity_settings.xml
│ │ │ ├── module_activity_socket_client.xml
│ │ │ ├── module_activity_socket_server.xml
│ │ │ ├── module_activity_summary.xml
│ │ │ ├── module_activity_summary_chart.xml
│ │ │ ├── module_activity_support.xml
│ │ │ ├── module_activity_sync_card_data.xml
│ │ │ ├── module_activity_test.xml
│ │ │ ├── module_activity_theme.xml
│ │ │ ├── module_activity_write_config.xml
│ │ │ ├── module_activity_zone_cards.xml
│ │ │ ├── module_activity_zone_events.xml
│ │ │ ├── module_dialog_card_preview.xml
│ │ │ ├── module_floating_window.xml
│ │ │ ├── module_fragment_card_list.xml
│ │ │ ├── module_fragment_deck_battle_detail_deck_detail.xml
│ │ │ ├── module_fragment_deck_battle_detail_summary.xml
│ │ │ ├── module_fragment_graveyard.xml
│ │ │ ├── module_fragment_list_mode.xml
│ │ │ ├── module_fragment_loading.xml
│ │ │ ├── module_fragment_opponent_hand_cards.xml
│ │ │ ├── module_header_summary.xml
│ │ │ ├── module_item_card.xml
│ │ │ ├── module_item_chip_filter.xml
│ │ │ ├── module_item_class_battle_detail.xml
│ │ │ ├── module_item_footer_with_fab.xml
│ │ │ ├── module_item_opponent_hand_card.xml
│ │ │ ├── module_item_record.xml
│ │ │ ├── module_item_summary_battle.xml
│ │ │ ├── module_item_text.xml
│ │ │ ├── module_item_zone_card.xml
│ │ │ └── module_item_zone_event_list_mode.xml
│ │ ├── values/
│ │ │ ├── arrays.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ ├── values-night/
│ │ │ └── themes.xml
│ │ └── values-zh-rCN/
│ │ └── strings.xml
│ └── test/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs_tracker/
│ └── module/
│ └── ExampleUnitTest.kt
├── settings.gradle
├── shared/
│ ├── .gitignore
│ ├── build.gradle
│ └── src/
│ └── main/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs/
│ └── shared/
│ └── entity/
│ └── CardType.kt
├── simulator/
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── ke/
│ │ └── hs/
│ │ └── simulator/
│ │ └── ExampleInstrumentedTest.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── ke/
│ │ └── hs/
│ │ └── simulator/
│ │ └── cards/
│ │ └── base/
│ │ ├── HeroCard.kt
│ │ ├── ICard.kt
│ │ ├── MinionCard.kt
│ │ ├── SpellCard.kt
│ │ └── WeaponCard.kt
│ └── test/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs/
│ └── simulator/
│ └── ExampleUnitTest.kt
└── writer/
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── release/
│ ├── output-metadata.json
│ └── writer-release.apk
└── src/
├── androidTest/
│ └── java/
│ └── com/
│ └── ke/
│ └── hs/
│ └── writer/
│ └── ExampleInstrumentedTest.kt
├── main/
│ ├── AndroidManifest.xml
│ └── res/
│ ├── drawable/
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── values-night/
│ └── themes.xml
└── test/
└── java/
└── com/
└── ke/
└── hs/
└── writer/
└── ExampleUnitTest.kt
Condensed preview — 279 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (637K chars).
[
{
"path": ".gitignore",
"chars": 225,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2023 keluokeda\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 426,
"preview": "# hs_tracker\n## Android14 use this https://github.com/keluokeda/Hs\nAn automatic Hearthstone tracker for Android\n#### 炉石传"
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle",
"chars": 2237,
"preview": "plugins {\n id 'com.android.application'\n id 'org.jetbrains.kotlin.android'\n id 'kotlin-kapt'\n id 'com.google"
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/release/output-metadata.json",
"chars": 378,
"preview": "{\n \"version\": 3,\n \"artifactType\": {\n \"type\": \"APK\",\n \"kind\": \"Directory\"\n },\n \"applicationId\": \"com.ke.hs_trac"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 163,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n\n <appl"
},
{
"path": "app/src/main/java/com/ke/hs_tracker/app/App.kt",
"chars": 167,
"preview": "package com.ke.hs_tracker.app\n\nimport com.ke.hs_tracker.module.MainApplication\nimport dagger.hilt.android.HiltAndroidApp"
},
{
"path": "app/src/main/java/com/ke/hs_tracker/app/MainActivity.kt",
"chars": 3745,
"preview": "package com.ke.hs_tracker.app\n//\n//import android.os.Bundle\n//import androidx.appcompat.app.AppCompatActivity\n//import a"
},
{
"path": "build.gradle",
"chars": 390,
"preview": "\n\nplugins {\n id 'com.android.application' version '8.0.2' apply false\n id 'com.android.library' version '8.0.2' ap"
},
{
"path": "core/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "core/build.gradle",
"chars": 500,
"preview": "plugins {\n id 'java-library'\n id 'org.jetbrains.kotlin.jvm'\n id 'kotlin-kapt'\n}\n\njava {\n sourceCompatibility"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/api/HearthStoneJsonApi.kt",
"chars": 481,
"preview": "package com.ke.hs_tracker.core.api\n\nimport com.ke.hs_tracker.core.entity.Card\nimport retrofit2.http.GET\nimport retrofit2"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/BlockType.kt",
"chars": 523,
"preview": "package com.ke.hs_tracker.core.entity\n\nenum class BlockType {\n /**\n * 攻击\n */\n Attack,\n\n /**\n * 死亡\n "
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/Card.kt",
"chars": 1108,
"preview": "package com.ke.hs_tracker.core.entity\n\nimport com.squareup.moshi.FromJson\nimport com.squareup.moshi.JsonClass\nimport com"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/CardClass.kt",
"chars": 231,
"preview": "package com.ke.hs_tracker.core.entity\n\nenum class CardClass {\n /**\n * 法师\n */\n Mage,\n\n /**\n * 术士\n "
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/CurrentDeck.kt",
"chars": 108,
"preview": "package com.ke.hs_tracker.core.entity\n\ndata class CurrentDeck(\n val name: String,\n val code: String\n)\n"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/Entity.kt",
"chars": 3731,
"preview": "package com.ke.hs_tracker.core.entity\n\nimport com.ke.hs_tracker.core.parser.PowerParserImpl\n\n\n//有三种形式\n//1,Entity=[entity"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/GameCardType.kt",
"chars": 643,
"preview": "package com.ke.hs_tracker.core.entity\n\n/**\n * 卡牌类型\n */\nenum class GameCardType {\n\n Game,\n\n /**\n * 玩家\n */\n "
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/InsertStackResult.kt",
"chars": 581,
"preview": "package com.ke.hs_tracker.core.entity\n\nsealed interface InsertStackResult {\n\n\n /**\n * 插入成功\n */\n object Suc"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/LogType.kt",
"chars": 184,
"preview": "package com.ke.hs_tracker.core.entity\n\nenum class LogType(val replace: String) {\n PowerTaskList(\"PowerTaskList.DebugP"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/Mechanics.kt",
"chars": 1027,
"preview": "package com.ke.hs_tracker.core.entity\n\n/**\n * 类型\n */\nenum class Mechanics {\n\n /**\n * 过载\n */\n Overload,\n\n "
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/NestedTag.kt",
"chars": 1461,
"preview": "package com.ke.hs_tracker.core.entity\n\ninternal sealed interface NestedTag {\n object CreateGame : NestedTag\n data "
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/PowerTag.kt",
"chars": 5335,
"preview": "package com.ke.hs_tracker.core.entity\n\n\nsealed interface PowerTag {\n\n\n sealed interface PowerTaskList : PowerTag {\n\n\n"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/entity/Zone.kt",
"chars": 430,
"preview": "package com.ke.hs_tracker.core.entity\n\n\nenum class Zone {\n /**\n * 战场\n */\n Play,\n\n /**\n * 牌库\n */"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/extensions.kt",
"chars": 793,
"preview": "package com.ke.hs_tracker.core\n\nimport com.ke.hs_tracker.core.parser.PowerParserImpl\nimport java.util.*\n\n\nfun String.rem"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/parser/BlockTagStack.kt",
"chars": 18854,
"preview": "package com.ke.hs_tracker.core.parser\n\nimport com.ke.hs_tracker.core.entity.*\nimport java.lang.Exception\nimport java.uti"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/parser/DeckFileObserver.kt",
"chars": 1694,
"preview": "package com.ke.hs_tracker.core.parser\n\nimport com.ke.hs_tracker.core.entity.CurrentDeck\nimport com.ke.hs_tracker.core.re"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/parser/PowerFileObserver.kt",
"chars": 1058,
"preview": "package com.ke.hs_tracker.core.parser\n\nimport com.ke.hs_tracker.core.entity.CurrentDeck\nimport com.ke.hs_tracker.core.en"
},
{
"path": "core/src/main/java/com/ke/hs_tracker/core/parser/PowerParser.kt",
"chars": 6470,
"preview": "package com.ke.hs_tracker.core.parser\n\nimport com.ke.hs_tracker.core.entity.*\nimport com.ke.hs_tracker.core.entity.toCar"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Tue Jan 18 13:48:58 CST 2022\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributio"
},
{
"path": "gradle.properties",
"chars": 1439,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "libs.versions.toml",
"chars": 3230,
"preview": "[versions]\ncompilesdk = \"33\"\nminsdk = \"24\"\ntargetsdk = \"33\"\n\nretrofit = \"2.9.0\"\nconstraintlayout = \"2.1.2\"\nmaterial = \"1"
},
{
"path": "module/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "module/build.gradle",
"chars": 3023,
"preview": "plugins {\n id 'com.android.library'\n id 'org.jetbrains.kotlin.android'\n id 'kotlin-kapt'\n id 'com.google.dag"
},
{
"path": "module/consumer-rules.pro",
"chars": 0,
"preview": ""
},
{
"path": "module/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "module/schemas/com.ke.hs_tracker.module.db.Database/1.json",
"chars": 7588,
"preview": "{\n \"formatVersion\": 1,\n \"database\": {\n \"version\": 1,\n \"identityHash\": \"d83bd3696913c5f4307118a1060d27e4\",\n \"e"
},
{
"path": "module/schemas/com.ke.hs_tracker.module.db.Database/2.json",
"chars": 9411,
"preview": "{\n \"formatVersion\": 1,\n \"database\": {\n \"version\": 2,\n \"identityHash\": \"1bb7b04a91def09f83b0a81252d181ba\",\n \"e"
},
{
"path": "module/schemas/com.ke.hs_tracker.module.db.Database/3.json",
"chars": 9821,
"preview": "{\n \"formatVersion\": 1,\n \"database\": {\n \"version\": 3,\n \"identityHash\": \"720ca460a50c2d8a7f6bf96c98cf4358\",\n \"e"
},
{
"path": "module/schemas/com.ke.hs_tracker.module.db.Database/4.json",
"chars": 9821,
"preview": "{\n \"formatVersion\": 1,\n \"database\": {\n \"version\": 4,\n \"identityHash\": \"a2944b7e136b6abe232a7a446c5ea3db\",\n \"e"
},
{
"path": "module/src/androidTest/java/com/ke/hs_tracker/module/ExampleInstrumentedTest.kt",
"chars": 680,
"preview": "package com.ke.hs_tracker.module\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.jun"
},
{
"path": "module/src/main/AndroidManifest.xml",
"chars": 4419,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <uses-"
},
{
"path": "module/src/main/assets/Decks.log",
"chars": 3266,
"preview": "I 22:23:04.0780690 Deck Contents Received:\nI 22:23:04.0780690 ### 狗贼\nI 22:23:04.0780690 # Deck ID: 7996212750\nI 22:23:04"
},
{
"path": "module/src/main/assets/log.config",
"chars": 951,
"preview": "[Bob]\nLogLevel=1\nFilePrinting=True\nConsolePrinting=False\nScreenPrinting=False\nVerbose=True\n[Power]\nLogLevel=1\nFilePrinti"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/MainApplication.kt",
"chars": 5859,
"preview": "package com.ke.hs_tracker.module\n\nimport android.app.Application\nimport android.content.Context\nimport android.net.Uri\ni"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/api/HearthStoneJsonApi.kt",
"chars": 485,
"preview": "package com.ke.hs_tracker.module.api\n\nimport com.ke.hs_tracker.module.entity.Card\nimport retrofit2.http.GET\nimport retro"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/data/PreferenceStorage.kt",
"chars": 1655,
"preview": "package com.ke.hs_tracker.module.data\n\nimport android.content.Context\nimport androidx.appcompat.app.AppCompatDelegate\nim"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/CardClassesConvert.kt",
"chars": 825,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.TypeConverter\nimport com.ke.hs_tracker.module.entity.CardClass"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/CardDao.kt",
"chars": 447,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.Query\nimp"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/Database.kt",
"chars": 1049,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.*\nimport androidx.room.Database\nimport androidx.room.migration"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/Game.kt",
"chars": 1957,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Pr"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/GameDao.kt",
"chars": 1209,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.*\nimport com.ke.hs_tracker.module.entity.CardClass\n\n@Dao\ninter"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/MechanicsListConvert.kt",
"chars": 1470,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.TypeConverter\nimport com.ke.hs_tracker.module.entity.CardClass"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/ZonePositionChangedEvent.kt",
"chars": 3212,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Pr"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/db/ZonePositionChangedEventDao.kt",
"chars": 543,
"preview": "package com.ke.hs_tracker.module.db\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.Query\n\n@D"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/di/CoroutinesModule.kt",
"chars": 1378,
"preview": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/di/CoroutinesQualifiers.kt",
"chars": 1090,
"preview": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/di/LogFileDirQualifiers.kt",
"chars": 833,
"preview": "/*\n * Copyright 2019 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/di/Module.kt",
"chars": 3153,
"preview": "package com.ke.hs_tracker.module.di\n\nimport android.content.Context\nimport androidx.room.Room\nimport com.ke.hs_tracker.m"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/ClearCardTableUseCase.kt",
"chars": 504,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.db.CardDao\nimport com.ke.hs_tracker.module.di.I"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/GetAllCardUseCase.kt",
"chars": 571,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.db.CardDao\nimport com.ke.hs_tracker.module.di.I"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/GetCardListUseCase.kt",
"chars": 697,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.api.HearthStoneJsonApi\nimport com.ke.hs_tracker"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/GetDatabaseCardCountUseCase.kt",
"chars": 521,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.db.CardDao\nimport com.ke.hs_tracker.module.di.I"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/GetLocalLogDirUseCase.kt",
"chars": 1191,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport android.content.Context\nimport androidx.documentfile.provider.DocumentFi"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/GetRealLogDirUseCase.kt",
"chars": 1291,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport android.content.Context\nimport androidx.documentfile.provider.DocumentFi"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/GetSaveLogFileEnableUseCase.kt",
"chars": 576,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.data.PreferenceStorage\nimport com.ke.hs_tracker"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/InsertCardListToDatabaseUseCase.kt",
"chars": 577,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.db.CardDao\nimport com.ke.hs_tracker.module.di.I"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/ParseDeckCodeUseCase.kt",
"chars": 3463,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport android.util.Base64\nimport com.ke.hs_tracker.module.db.CardDao\nimport co"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/SaveLogFileUseCase.kt",
"chars": 1175,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport android.content.Context\nimport android.net.Uri\nimport com.ke.hs_tracker."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/SetSaveLogFileEnableUseCase.kt",
"chars": 614,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport com.ke.hs_tracker.module.data.PreferenceStorage\nimport com.ke.hs_tracker"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/domain/WriteLogConfigFileUseCase.kt",
"chars": 1792,
"preview": "package com.ke.hs_tracker.module.domain\n\nimport android.content.Context\nimport androidx.documentfile.provider.DocumentFi"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/BlockType.kt",
"chars": 525,
"preview": "package com.ke.hs_tracker.module.entity\n\nenum class BlockType {\n /**\n * 攻击\n */\n Attack,\n\n /**\n * 死亡"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Card.kt",
"chars": 1246,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.P"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/CardBean.kt",
"chars": 561,
"preview": "package com.ke.hs_tracker.module.entity\n\ndata class CardBean(\n val card: Card,\n val count: Int = 0\n) {\n //防止在使用"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/CardClass.kt",
"chars": 2369,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport androidx.annotation.ColorRes\nimport androidx.annotation.DrawableRes\nimpo"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/CardType.kt",
"chars": 675,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport com.squareup.moshi.FromJson\nimport com.squareup.moshi.ToJson\n\n\nenum clas"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/CurrentDeck.kt",
"chars": 110,
"preview": "package com.ke.hs_tracker.module.entity\n\ndata class CurrentDeck(\n val name: String,\n val code: String\n)\n"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Entity.kt",
"chars": 3880,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport com.ke.hs_tracker.module.parser.PowerParserImpl\n\n\n//有三种形式\n//1,Entity=[en"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/EntityWithPayload.kt",
"chars": 317,
"preview": "package com.ke.hs_tracker.module.entity\n\ndata class EntityWithPayload(\n val entity: Entity,\n private val payloads:"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/EnumMoshiAdapter.kt",
"chars": 461,
"preview": "package com.ke.hs_tracker.module.entity\n\nobject EnumMoshiAdapter {\n\n\n fun <T : Enum<T>> fromJson(value: String, enumL"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/FormatType.kt",
"chars": 534,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport androidx.annotation.StringRes\nimport com.ke.hs_tracker.module.R\n\nenum cl"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/GameCardType.kt",
"chars": 645,
"preview": "package com.ke.hs_tracker.module.entity\n\n/**\n * 卡牌类型\n */\nenum class GameCardType {\n\n Game,\n\n /**\n * 玩家\n */"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/GameEvent.kt",
"chars": 655,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport com.ke.hs_tracker.module.db.Game\n\n/**\n * 游戏事件\n */\nsealed interface GameE"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/GameType.kt",
"chars": 459,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport androidx.annotation.StringRes\nimport com.ke.hs_tracker.module.R\n\nenum cl"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/GraveyardCard.kt",
"chars": 164,
"preview": "package com.ke.hs_tracker.module.entity\n\ndata class GraveyardCard(\n val card: Card,\n /**\n * 插入时间\n */\n v"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/InsertStackResult.kt",
"chars": 583,
"preview": "package com.ke.hs_tracker.module.entity\n\nsealed interface InsertStackResult {\n\n\n /**\n * 插入成功\n */\n object S"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/LogType.kt",
"chars": 186,
"preview": "package com.ke.hs_tracker.module.entity\n\nenum class LogType(val replace: String) {\n PowerTaskList(\"PowerTaskList.Debu"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Mechanics.kt",
"chars": 3929,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport com.squareup.moshi.FromJson\nimport com.squareup.moshi.ToJson\n\n/**\n * 类型\n"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/NestedTag.kt",
"chars": 1492,
"preview": "package com.ke.hs_tracker.module.entity\n\ninternal sealed interface NestedTag {\n object CreateGame : NestedTag\n dat"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/PowerTag.kt",
"chars": 14083,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport com.ke.hs_tracker.module.db.ZonePositionChangedEvent\n\n\nsealed interface "
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Race.kt",
"chars": 1713,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport androidx.annotation.StringRes\nimport com.ke.hs_tracker.module.R\nimport c"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Rarity.kt",
"chars": 1041,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport androidx.annotation.ColorRes\nimport androidx.annotation.StringRes\nimport"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/SpellSchool.kt",
"chars": 1071,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport androidx.annotation.StringRes\nimport com.ke.hs_tracker.module.R\nimport c"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Turn.kt",
"chars": 977,
"preview": "package com.ke.hs_tracker.module.entity\n\nsealed interface Turn {\n\n\n /**\n * 游戏初始化\n */\n data class InitialGa"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/Zone.kt",
"chars": 1030,
"preview": "package com.ke.hs_tracker.module.entity\n\n\nenum class Zone {\n /**\n * 战场\n */\n Play,\n\n /**\n * 牌库\n "
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/entity/ZoneCard.kt",
"chars": 218,
"preview": "package com.ke.hs_tracker.module.entity\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndat"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/parser/BlockTagStack.kt",
"chars": 19545,
"preview": "package com.ke.hs_tracker.module.parser\n\nimport com.ke.hs_tracker.module.entity.*\nimport com.orhanobut.logger.Logger\nimp"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/parser/DeckCardObserver.kt",
"chars": 9481,
"preview": "package com.ke.hs_tracker.module.parser\n\nimport android.content.Context\nimport androidx.documentfile.provider.DocumentFi"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/parser/DeckFileObserver.kt",
"chars": 1869,
"preview": "package com.ke.hs_tracker.module.parser\n\n\nimport android.os.Looper\nimport com.ke.hs_tracker.module.entity.CurrentDeck\nim"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/parser/PowerFileObserver.kt",
"chars": 1879,
"preview": "package com.ke.hs_tracker.module.parser\n\n\nimport android.os.Looper\nimport kotlinx.coroutines.delay\nimport kotlinx.corout"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/parser/PowerParser.kt",
"chars": 6626,
"preview": "package com.ke.hs_tracker.module.parser\n\nimport com.ke.hs_tracker.module.entity.Entity\nimport com.ke.hs_tracker.module.e"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/parser/PowerTagHandler.kt",
"chars": 13678,
"preview": "package com.ke.hs_tracker.module.parser\n\nimport com.ke.hs_tracker.module.db.Game\nimport com.ke.hs_tracker.module.domain."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/service/ItemViewTouchListener.kt",
"chars": 1178,
"preview": "package com.ke.hs_tracker.module.service\n\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.W"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/service/ScaleTouchListener.kt",
"chars": 1372,
"preview": "package com.ke.hs_tracker.module.service\n\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.W"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/service/WindowService.kt",
"chars": 5577,
"preview": "package com.ke.hs_tracker.module.service\n\nimport android.content.Intent\nimport android.os.Build\nimport android.os.IBinde"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/chart/GetSummaryChartViewDataUseCase.kt",
"chars": 1968,
"preview": "package com.ke.hs_tracker.module.ui.chart\n\nimport com.ke.hs_tracker.module.db.GameDao\nimport com.ke.hs_tracker.module.di"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/chart/PieChartData.kt",
"chars": 189,
"preview": "package com.ke.hs_tracker.module.ui.chart\n\nimport androidx.annotation.ColorRes\n\n data class PieChartData(\n val label"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/chart/SummaryChartActivity.kt",
"chars": 6724,
"preview": "package com.ke.hs_tracker.module.ui.chart\n\nimport android.graphics.Color\nimport android.os.Bundle\nimport androidx.appcom"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/chart/SummaryChartViewData.kt",
"chars": 687,
"preview": "package com.ke.hs_tracker.module.ui.chart\n\nimport com.ke.hs_tracker.module.entity.CardClass\n\ninternal data class Summary"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/classbattledetail/ClassBattleDetailActivity.kt",
"chars": 3341,
"preview": "package com.ke.hs_tracker.module.ui.classbattledetail\n\nimport android.content.Context\nimport android.content.Intent\nimpo"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/classbattledetail/ClassBattleDetailViewModel.kt",
"chars": 942,
"preview": "package com.ke.hs_tracker.module.ui.classbattledetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecy"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/classbattledetail/ClassBattleItem.kt",
"chars": 244,
"preview": "package com.ke.hs_tracker.module.ui.classbattledetail\n\nimport com.ke.hs_tracker.module.entity.CardClass\n\ninternal data c"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/classbattledetail/GetClassBattleItemListUseCase.kt",
"chars": 1162,
"preview": "package com.ke.hs_tracker.module.ui.classbattledetail\n\nimport com.ke.hs_tracker.module.db.GameDao\nimport com.ke.hs_track"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/common/CardAdapter.kt",
"chars": 1894,
"preview": "package com.ke.hs_tracker.module.ui.common\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport andr"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/common/LoadingFragment.kt",
"chars": 592,
"preview": "package com.ke.hs_tracker.module.ui.common\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.v"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deck/DeckCodeParserActivity.kt",
"chars": 2086,
"preview": "package com.ke.hs_tracker.module.ui.deck\n\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport androidx.a"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deck/DeckCodeParserViewModel.kt",
"chars": 1084,
"preview": "package com.ke.hs_tracker.module.ui.deck\n\nimport androidx.lifecycle.viewModelScope\nimport com.ke.hs_tracker.module.domai"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/BattleRecordsFragment.kt",
"chars": 1679,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/BattleRecordsViewModel.kt",
"chars": 1158,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecyc"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/DeckBattleDetailActivity.kt",
"chars": 2268,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport android.content.Context\nimport android.content.Intent\nimpor"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/DeckDetailFragment.kt",
"chars": 4213,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport android.graphics.Color\nimport android.os.Bundle\nimport andr"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/DeckDetailViewModel.kt",
"chars": 4240,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecyc"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/DeckFragment.kt",
"chars": 519,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport androidx.fragment.app.activityViewModels\nimport com.ke.hs_t"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/DeckViewModel.kt",
"chars": 1090,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecyc"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/GetGamesByDeckCodeAndNameUseCase.kt",
"chars": 707,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport com.ke.hs_tracker.module.db.Game\nimport com.ke.hs_tracker.m"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/SummaryFragment.kt",
"chars": 3578,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/deckbattledetail/SummaryViewModel.kt",
"chars": 1381,
"preview": "package com.ke.hs_tracker.module.ui.deckbattledetail\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecyc"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/diagnose/DiagnoseActivity.kt",
"chars": 982,
"preview": "package com.ke.hs_tracker.module.ui.diagnose\n\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\ni"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/filter/FilterActivity.kt",
"chars": 8275,
"preview": "package com.ke.hs_tracker.module.ui.filter\n\nimport android.content.Context\nimport android.os.Bundle\nimport androidx.appc"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/CardListFragment.kt",
"chars": 1635,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.vie"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/DeckCardListFragment.kt",
"chars": 466,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport androidx.fragment.app.activityViewModels\nimport com.ke.hs_tracker.modul"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/GraveyardFragment.kt",
"chars": 5912,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.os.Parce"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/MainActivity.kt",
"chars": 4351,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport androidx.a"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/MainViewModel.kt",
"chars": 24560,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport a"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/OpponentGraveyardFragment.kt",
"chars": 496,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport androidx.fragment.app.activityViewModels\nimport com.ke.hs_tracker.modul"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/OpponentHandCardsFragment.kt",
"chars": 2343,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.vie"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/main/UserGraveyardFragment.kt",
"chars": 488,
"preview": "package com.ke.hs_tracker.module.ui.main\n\nimport androidx.fragment.app.activityViewModels\nimport com.ke.hs_tracker.modul"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/migrate/MigrateDataConvert.kt",
"chars": 1283,
"preview": "package com.ke.hs_tracker.module.ui.migrate\n\nimport androidx.annotation.WorkerThread\nimport com.ke.hs_tracker.module.db."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/migrate/MigrateMainActivity.kt",
"chars": 1480,
"preview": "package com.ke.hs_tracker.module.ui.migrate\n\nimport android.content.Context\nimport android.content.Intent\nimport android"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/migrate/SocketClientActivity.kt",
"chars": 2201,
"preview": "package com.ke.hs_tracker.module.ui.migrate\n\nimport android.app.ProgressDialog\nimport android.os.Bundle\nimport androidx."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/migrate/SocketServerActivity.kt",
"chars": 1964,
"preview": "package com.ke.hs_tracker.module.ui.migrate\n\nimport android.app.ProgressDialog\nimport android.os.Bundle\nimport androidx."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/permissions/PermissionsActivity.kt",
"chars": 7133,
"preview": "package com.ke.hs_tracker.module.ui.permissions\n\nimport android.content.Context\nimport android.content.Intent\nimport and"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/permissions/PermissionsViewModel.kt",
"chars": 1792,
"preview": "package com.ke.hs_tracker.module.ui.permissions\n\nimport android.content.Context\nimport androidx.lifecycle.viewModelScope"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/records/RecordAdapter.kt",
"chars": 1462,
"preview": "package com.ke.hs_tracker.module.ui.records\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport com"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/records/RecordsActivity.kt",
"chars": 2759,
"preview": "package com.ke.hs_tracker.module.ui.records\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.acti"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/records/RecordsViewModel.kt",
"chars": 821,
"preview": "package com.ke.hs_tracker.module.ui.records\n\nimport androidx.lifecycle.viewModelScope\nimport com.ke.hs_tracker.module.db"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/settings/SettingsActivity.kt",
"chars": 4124,
"preview": "package com.ke.hs_tracker.module.ui.settings\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bun"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/settings/SettingsViewModel.kt",
"chars": 1226,
"preview": "package com.ke.hs_tracker.module.ui.settings\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelSco"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/splash/SplashActivity.kt",
"chars": 2514,
"preview": "package com.ke.hs_tracker.module.ui.splash\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport "
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/splash/SplashViewModel.kt",
"chars": 1768,
"preview": "package com.ke.hs_tracker.module.ui.splash\n\nimport android.content.Context\nimport androidx.lifecycle.AndroidViewModel\nim"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/BattleRateItem.kt",
"chars": 1318,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport androidx.annotation.IntRange\nimport com.ke.hs_tracker.module.entity."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/BattleRateItemAdapter.kt",
"chars": 2413,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport com"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/BattleRateListFragment.kt",
"chars": 2280,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/BattleRateListViewModel.kt",
"chars": 843,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycl"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/DeckBattleRateListViewModel.kt",
"chars": 324,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport javax.inject.Inje"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/GetDeckBattleRateListUseCase.kt",
"chars": 1301,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport com.ke.hs_tracker.module.db.GameDao\nimport com.ke.hs_tracker.module."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/GetHeroBattleRateListUseCase.kt",
"chars": 1489,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport com.ke.hs_tracker.module.db.GameDao\nimport com.ke.hs_tracker.module."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/HeroBattleRateListViewModel.kt",
"chars": 325,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport javax.inject.Inje"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/RateByDeckFragment.kt",
"chars": 278,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidE"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/RateByHeroFragment.kt",
"chars": 279,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport androidx.fragment.app.viewModels\nimport dagger.hilt.android.AndroidE"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/summary/SummaryActivity.kt",
"chars": 4375,
"preview": "package com.ke.hs_tracker.module.ui.summary\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bund"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/support/SupportActivity.kt",
"chars": 518,
"preview": "package com.ke.hs_tracker.module.ui.support\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nim"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/sync/SyncCardDataActivity.kt",
"chars": 2801,
"preview": "package com.ke.hs_tracker.module.ui.sync\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.activit"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/sync/SyncCardDataViewModel.kt",
"chars": 2515,
"preview": "package com.ke.hs_tracker.module.ui.sync\n\nimport androidx.lifecycle.SavedStateHandle\nimport androidx.lifecycle.viewModel"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/test/CreateRecordActivity.kt",
"chars": 4250,
"preview": "package com.ke.hs_tracker.module.ui.test\n\nimport android.content.DialogInterface\nimport androidx.appcompat.app.AppCompat"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/test/LocalFileParserActivity.kt",
"chars": 378,
"preview": "package com.ke.hs_tracker.module.ui.test\n\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimpor"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/test/TestActivity.kt",
"chars": 3502,
"preview": "package com.ke.hs_tracker.module.ui.test\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.appcomp"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/theme/ThemeActivity.kt",
"chars": 1604,
"preview": "package com.ke.hs_tracker.module.ui.theme\n\nimport android.os.Bundle\nimport android.widget.CompoundButton\nimport androidx"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/writeconfig/WriteConfigActivity.kt",
"chars": 2465,
"preview": "package com.ke.hs_tracker.module.ui.writeconfig\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx."
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/zonecards/ZoneCardsActivity.kt",
"chars": 2469,
"preview": "package com.ke.hs_tracker.module.ui.zonecards\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport androi"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/zoneevents/ListModeFragment.kt",
"chars": 5181,
"preview": "package com.ke.hs_tracker.module.ui.zoneevents\n\nimport android.os.Bundle\nimport androidx.fragment.app.Fragment\nimport an"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/zoneevents/ZoneEventsActivity.kt",
"chars": 2490,
"preview": "package com.ke.hs_tracker.module.ui.zoneevents\n\nimport android.content.Intent\nimport androidx.appcompat.app.AppCompatAct"
},
{
"path": "module/src/main/java/com/ke/hs_tracker/module/ui/zoneevents/ZoneEventsViewModel.kt",
"chars": 8507,
"preview": "package com.ke.hs_tracker.module.ui.zoneevents\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport androidx.li"
},
{
"path": "module/src/main/res/color/module_game_state.xml",
"chars": 275,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item "
},
{
"path": "module/src/main/res/drawable/module_baseline_arrow_back_white_24dp.xml",
"chars": 433,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_clear_black_24dp.xml",
"chars": 471,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_clear_red_500_24dp.xml",
"chars": 471,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_done_black_24dp.xml",
"chars": 394,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_done_green_500_24dp.xml",
"chars": 394,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_done_white_24dp.xml",
"chars": 394,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_drag_handle_white_24dp.xml",
"chars": 402,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_keyboard_arrow_right_grey_500_24dp.xml",
"chars": 428,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_pie_chart_white_24dp.xml",
"chars": 541,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_play_arrow_white_24dp.xml",
"chars": 384,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_settings_white_24dp.xml",
"chars": 1247,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_sync_white_24dp.xml",
"chars": 596,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable/module_baseline_zoom_out_map_white_24dp.xml",
"chars": 592,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android"
},
{
"path": "module/src/main/res/drawable-xxxhdpi/module_bg_splash.xml",
"chars": 400,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item"
},
{
"path": "module/src/main/res/layout/module_activity_class_battle_detail.xml",
"chars": 3417,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "module/src/main/res/layout/module_activity_create_record.xml",
"chars": 8437,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "module/src/main/res/layout/module_activity_deck_battle_detail.xml",
"chars": 1385,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "module/src/main/res/layout/module_activity_deck_code_parser.xml",
"chars": 2704,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "module/src/main/res/layout/module_activity_diagnose.xml",
"chars": 1224,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "module/src/main/res/layout/module_activity_filter.xml",
"chars": 5142,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "module/src/main/res/layout/module_activity_local_file_parser.xml",
"chars": 444,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "module/src/main/res/layout/module_activity_main.xml",
"chars": 1838,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
}
]
// ... and 79 more files (download for full content)
About this extraction
This page contains the full source code of the keluokeda/hs_tracker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 279 files (562.9 KB), approximately 143.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.