diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..75849be --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +registries: + maven-google: + type: maven-repository + url: https://maven.google.com + username: "" + password: "" + maven-center: + type: maven-repository + url: https://repo.maven.apache.org/maven2/ + username: "" + password: "" +updates: + - package-ecosystem: "gradle" + directory: "/" + registries: + - maven-center + - maven-google + + schedule: + interval: "daily" + + - package-ecosystem: "github-actions" + directory: ".github/workflows" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6493eb8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,33 @@ +name: Android Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'true' + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew :app:assembleDebug + - name: Upload a Build Artifact + if: success() + uses: actions/upload-artifact@v4.4.3 + with: + name: build-artifact + path: | + ./**/*.apk \ No newline at end of file diff --git a/.github/workflows/unitTest.yml.bak b/.github/workflows/unitTest.yml.bak new file mode 100644 index 0000000..9c0e7f0 --- /dev/null +++ b/.github/workflows/unitTest.yml.bak @@ -0,0 +1,26 @@ +name: Unit Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: 'true' + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew :app:test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2b8a1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,112 @@ +# IntelliJ +*.iml +*.ipr +.idea/ +out/ + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Android template +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties +keystore.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Kotlin +.kotlin/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bf449ed --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "TvBoxPlugin"] + path = TvBoxPlugin + url = https://github.com/muedsa/TvBoxPlugin.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bbfc085 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 MUEDSA + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc092ec --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# TvBoxDemoPlugin +[TvBox](https://github.com/muedsa/TvBox)的demo插件 + +## Use this template(使用此仓库作为模板) +本仓库使用**git submodule**,请在项目Clone后使用`git submodule update --init --recursive`拉取子模块。 +你需要修改以下位置 +- [ ] [settings.gradle.kts](settings.gradle.kts) 中的 `rootProject.name = "你的项目名称"` +- [ ] [app/src/main/res/values/strings.xml](app/src/main/res/values/strings.xml) 中的 `你的插件名称` +- [ ] [app/build.gradle.kts](app/build.gradle.kts) 中的 `namespace = "你的namespace"` +- [ ] [app/build.gradle.kts](app/build.gradle.kts) 中的 `applicationId = "applicationId"` +- [ ] [app/build.gradle.kts](app/build.gradle.kts) 中的 `signingConfigs { // 你的签名 }` +- [ ] [app/src/main/res/mipmap-xxxx](app/src/main/res) 中的 [ic_launcher](app/src/main/res/mipmap-hdpi/ic_launcher.webp) 为你的Icon +- [ ] 编写代码实现插件IPlugin的所有功能,并修改 [app/src/main/AndroidManifest.xml](app/src/main/AndroidManifest.xml) 中的 `` +- [ ] [README.md](README.md) diff --git a/TvBoxPlugin b/TvBoxPlugin new file mode 160000 index 0000000..c784797 --- /dev/null +++ b/TvBoxPlugin @@ -0,0 +1 @@ +Subproject commit c78479741770c132012bfd37f2d695415fcf56fa diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..3698436 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,80 @@ +import java.io.FileInputStream +import java.util.Properties + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} + +val keystorePropertiesFile: File = rootProject.file("keystore.properties") +val keystoreProperties = Properties() +if (keystorePropertiesFile.exists() && keystorePropertiesFile.canRead()) { + keystoreProperties.load(FileInputStream(keystorePropertiesFile)) +} + +android { + namespace = "com.muedsa.tvbox.demoplugin" + compileSdk = 35 + + defaultConfig { + applicationId = "com.muedsa.tvbox.demoplugin" + minSdk = 24 + targetSdk = 35 + versionCode = 1 + versionName = "0.0.1" + } + + signingConfigs { + create("release") { + if (keystoreProperties.containsKey("muedsa.signingConfig.storeFile")) { + storeFile = file(keystoreProperties["muedsa.signingConfig.storeFile"] as String) + storePassword = keystoreProperties["muedsa.signingConfig.storePassword"] as String + keyAlias = keystoreProperties["muedsa.signingConfig.keyAlias"] as String + keyPassword = keystoreProperties["muedsa.signingConfig.keyPassword"] as String + } else { + val debugSigningConfig = signingConfigs.getByName("debug") + storeFile = debugSigningConfig.storeFile + storePassword = debugSigningConfig.storePassword + keyAlias = debugSigningConfig.keyAlias + keyPassword = debugSigningConfig.keyPassword + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("release") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + // 修改APK文件名 + applicationVariants.all { + outputs.all { + if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) { + outputFileName = "${rootProject.name}-${versionName}-${buildType.name}.apk.tbp" + } + } + } +} +dependencies { + //implementation(libs.androidx.core.ktx) + compileOnly(project(":api")) + testImplementation(project(":api")) + testImplementation(libs.junit4) + testImplementation(libs.kotlinx.coroutines.test) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..43e0af9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/DemoPlugin.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/DemoPlugin.kt new file mode 100644 index 0000000..7cb5dca --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/DemoPlugin.kt @@ -0,0 +1,62 @@ +package com.muedsa.tvbox.demoplugin + +import com.muedsa.tvbox.api.plugin.IPlugin +import com.muedsa.tvbox.api.plugin.PluginOptions +import com.muedsa.tvbox.api.plugin.TvBoxContext +import com.muedsa.tvbox.api.service.IMainScreenService +import com.muedsa.tvbox.api.service.IMediaCatalogService +import com.muedsa.tvbox.api.service.IMediaDetailService +import com.muedsa.tvbox.api.service.IMediaSearchService +import com.muedsa.tvbox.api.store.IPluginPerfStore +import com.muedsa.tvbox.demoplugin.service.DanDanPlayApiService +import com.muedsa.tvbox.demoplugin.service.MainScreenService +import com.muedsa.tvbox.demoplugin.service.MediaCatalogService +import com.muedsa.tvbox.demoplugin.service.MediaDetailService +import com.muedsa.tvbox.demoplugin.service.MediaSearchService +import com.muedsa.tvbox.tool.IPv6Checker +import com.muedsa.tvbox.tool.PluginCookieJar +import com.muedsa.tvbox.tool.SharedCookieSaver +import com.muedsa.tvbox.tool.createJsonRetrofit +import com.muedsa.tvbox.tool.createOkHttpClient +import timber.log.Timber + +class DemoPlugin(tvBoxContext: TvBoxContext) : IPlugin(tvBoxContext = tvBoxContext) { + + private val store: IPluginPerfStore = tvBoxContext.store + + private val cookieSaver by lazy { SharedCookieSaver(store = store) } + + override var options: PluginOptions = PluginOptions(enableDanDanPlaySearch = true) + + override suspend fun onInit() {} + + override suspend fun onLaunched() { + val count = store.getOrDefault(key = LAUNCH_COUNT_PREF_KEY, default = 0) + 1 + Timber.i("DemoPlugin launched, count:$count") + store.update(key = LAUNCH_COUNT_PREF_KEY, value = count) + } + + private val danDanPlayApiService by lazy { + createJsonRetrofit( + baseUrl = "https://api.dandanplay.net/api/", + service = DanDanPlayApiService::class.java, + okHttpClient = createOkHttpClient( + debug = tvBoxContext.debug, + cookieJar = PluginCookieJar(saver = cookieSaver), + onlyIpv4 = tvBoxContext.iPv6Status != IPv6Checker.IPv6Status.SUPPORTED + ) + ) + } + private val mainScreenService by lazy { MainScreenService(danDanPlayApiService) } + private val mediaDetailService by lazy { MediaDetailService(danDanPlayApiService) } + private val mediaSearchService by lazy { MediaSearchService(danDanPlayApiService) } + private val mediaCatalogService by lazy { MediaCatalogService(danDanPlayApiService) } + + override fun provideMainScreenService(): IMainScreenService = mainScreenService + + override fun provideMediaDetailService(): IMediaDetailService = mediaDetailService + + override fun provideMediaSearchService(): IMediaSearchService = mediaSearchService + + override fun provideMediaCatalogService(): IMediaCatalogService = mediaCatalogService +} \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/PluginPrefs.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/PluginPrefs.kt new file mode 100644 index 0000000..8546c4d --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/PluginPrefs.kt @@ -0,0 +1,5 @@ +package com.muedsa.tvbox.demoplugin + +import com.muedsa.tvbox.api.store.intPluginPerfKey + +val LAUNCH_COUNT_PREF_KEY = intPluginPerfKey("LAUNCH_COUNT") \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/helper/ListHelper.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/helper/ListHelper.kt new file mode 100644 index 0000000..15bfa9a --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/helper/ListHelper.kt @@ -0,0 +1,11 @@ +package com.muedsa.tvbox.demoplugin.helper + +fun splitListBySize(inputList: List, size: Int): List> { + val result = mutableListOf>() + var index = 0 + while (index < inputList.size) { + result.add(inputList.subList(index, (index + size).coerceAtMost(inputList.size))) + index += size + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiDetailsResp.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiDetailsResp.kt new file mode 100644 index 0000000..1caee99 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiDetailsResp.kt @@ -0,0 +1,12 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiDetailsResp( + @SerialName("success") val success: Boolean = false, + @SerialName("errorCode") val errorCode: Int = -1, + @SerialName("errorMessage") val errorMessage: String = "", + @SerialName("bangumi") val bangumi: BangumiInfo? = null +) diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiEpisode.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiEpisode.kt new file mode 100644 index 0000000..e792ce9 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiEpisode.kt @@ -0,0 +1,13 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiEpisode( + @SerialName("episodeId") val episodeId: Long, + @SerialName("episodeTitle") val episodeTitle: String, + @SerialName("episodeNumber") val episodeNumber: String, + @SerialName("lastWatched") val lastWatched: String? = null, + @SerialName("airDate") val airDate: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiInfo.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiInfo.kt new file mode 100644 index 0000000..9afa211 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiInfo.kt @@ -0,0 +1,33 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +@Serializable +data class BangumiInfo( + @SerialName("type") val type: String, + @SerialName("typeDescription") val typeDescription: String, + @SerialName("titles") val titles: List, + @SerialName("episodes") val episodes: List, + @SerialName("summary") val summary: String, + @SerialName("metadata") val metadata: List, + @SerialName("bangumiUrl") val bangumiUrl: String, +// @SerialName("userRating") val userRating: Int = 0, +// @SerialName("favoriteStatus") val favoriteStatus: Boolean? = false, +// @SerialName("comment") val comment: List? = null, + @SerialName("ratingDetails") val ratingDetails: Map, + // relateds + // similars + // tags + // onlineDatabases + @SerialName("animeId") val animeId: Int, + @SerialName("animeTitle") val animeTitle: String, + @SerialName("imageUrl") val imageUrl: String, + @SerialName("searchKeyword") val searchKeyword: String, + @SerialName("isOnAir") val isOnAir: Boolean, + @SerialName("airDay") val airDay: Int, + @SerialName("isFavorited") val isFavorited: Boolean = false, + @SerialName("isRestricted") val isRestricted: Boolean = false, + @SerialName("rating") val rating: Float, +) diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSearch.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSearch.kt new file mode 100644 index 0000000..c1da32d --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSearch.kt @@ -0,0 +1,19 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiSearch( + @SerialName("animeId") val animeId: Int, + @SerialName("animeTitle") val animeTitle: String, + @SerialName("type") val type: String, + @SerialName("typeDescription") val typeDescription: String, + @SerialName("imageUrl") val imageUrl: String, + @SerialName("startDate") val startDate: String, + @SerialName("episodeCount") val episodeCount: Int, + @SerialName("rating") val rating: Float, + @SerialName("isFavorited") val isFavorited: Boolean +) { + val startOnlyDate: String by lazy { startDate.substringBefore("T") } +} diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSearchResp.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSearchResp.kt new file mode 100644 index 0000000..cd02587 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSearchResp.kt @@ -0,0 +1,13 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiSearchResp( + @SerialName("hasMore") val hasMore: Boolean = false, + @SerialName("success") val success: Boolean = false, + @SerialName("errorCode") val errorCode: Int = -1, + @SerialName("errorMessage") val errorMessage: String = "", + @SerialName("animes") val animes: List? = null +) diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSeason.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSeason.kt new file mode 100644 index 0000000..c41abd6 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSeason.kt @@ -0,0 +1,11 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiSeason ( + @SerialName("year") val year: Int, + @SerialName("month") val month: Int, + @SerialName("seasonName") val seasonName: String +) \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSeasonsResp.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSeasonsResp.kt new file mode 100644 index 0000000..b43ea28 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiSeasonsResp.kt @@ -0,0 +1,12 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiSeasonsResp( + @SerialName("seasons") val seasons: List, + @SerialName("success") val success: Boolean = false, + @SerialName("errorCode") val errorCode: Int = -1, + @SerialName("errorMessage") val errorMessage: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiShin.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiShin.kt new file mode 100644 index 0000000..ad0b70a --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiShin.kt @@ -0,0 +1,17 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiShin( + @SerialName("animeId") val animeId: Int, + @SerialName("animeTitle") val animeTitle: String, + @SerialName("imageUrl") val imageUrl: String, + @SerialName("searchKeyword") val searchKeyword: String, + @SerialName("isOnAir") val isOnAir: Boolean, + @SerialName("airDay") val airDay: Int, + @SerialName("isFavorited") val isFavorited: Boolean = false, + @SerialName("isRestricted") val isRestricted: Boolean = false, + @SerialName("rating") val rating: Float, +) diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiShinResp.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiShinResp.kt new file mode 100644 index 0000000..9174ef5 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiShinResp.kt @@ -0,0 +1,12 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiShinResp( + @SerialName("bangumiList") val bangumiList: List = emptyList(), + @SerialName("success") val success: Boolean = false, + @SerialName("errorCode") val errorCode: Int = -1, + @SerialName("errorMessage") val errorMessage: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiTitle.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiTitle.kt new file mode 100644 index 0000000..55c2532 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/model/BangumiTitle.kt @@ -0,0 +1,10 @@ +package com.muedsa.tvbox.demoplugin.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BangumiTitle( + @SerialName("language") val language: String, + @SerialName("title") val title: String +) diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/service/DanDanPlayApiService.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/DanDanPlayApiService.kt new file mode 100644 index 0000000..7b1c67b --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/DanDanPlayApiService.kt @@ -0,0 +1,36 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.demoplugin.model.BangumiSearchResp +import com.muedsa.tvbox.demoplugin.model.BangumiDetailsResp +import com.muedsa.tvbox.demoplugin.model.BangumiSearch +import com.muedsa.tvbox.demoplugin.model.BangumiSeasonsResp +import com.muedsa.tvbox.demoplugin.model.BangumiShinResp +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface DanDanPlayApiService { + + @GET("v2/bangumi/shin") + suspend fun bangumiShin(): BangumiShinResp + + @GET("v2/search/anime") + suspend fun searchAnime( + @Query("keyword") keyword: String, + @Query("type") type: String = "" + ): BangumiSearchResp + + @GET("v2/bangumi/{animeId}") + suspend fun getAnime( + @Path("animeId") animeId: Int + ): BangumiDetailsResp + + @GET("v2/bangumi/season/anime") + suspend fun getSeasonYearMonth(): BangumiSeasonsResp + + @GET("v2/bangumi/season/anime/{year}/{month}") + suspend fun getSeasonAnime( + @Path("year") year: String, + @Path("month") month: String + ): BangumiShinResp +} \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MainScreenService.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MainScreenService.kt new file mode 100644 index 0000000..b086615 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MainScreenService.kt @@ -0,0 +1,38 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.api.data.MediaCard +import com.muedsa.tvbox.api.data.MediaCardRow +import com.muedsa.tvbox.api.service.IMainScreenService +import com.muedsa.tvbox.demoplugin.helper.splitListBySize + +class MainScreenService( + private val danDanPlayApiService: DanDanPlayApiService +) : IMainScreenService { + + private var rowSize: Int = 30 + + override suspend fun getRowsData(): List { + val resp = danDanPlayApiService.bangumiShin() + if (resp.errorCode != 0) { + throw RuntimeException(resp.errorMessage) + } + if (resp.bangumiList.isEmpty()) + return emptyList() + val rows = splitListBySize(resp.bangumiList, rowSize) + return rows.mapIndexed { index, row -> + MediaCardRow( + title = "新番列表 ${index + 1}", + cardWidth = 210 / 2, + cardHeight = 302 / 2, + list = row.map { + MediaCard( + id = it.animeId.toString(), + title = it.animeTitle, + detailUrl = it.animeId.toString(), + coverImageUrl = it.imageUrl + ) + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaCatalogService.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaCatalogService.kt new file mode 100644 index 0000000..a17b4fb --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaCatalogService.kt @@ -0,0 +1,113 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.api.data.MediaCard +import com.muedsa.tvbox.api.data.MediaCatalogConfig +import com.muedsa.tvbox.api.data.MediaCatalogOption +import com.muedsa.tvbox.api.data.MediaCatalogOptionItem +import com.muedsa.tvbox.api.data.PagingResult +import com.muedsa.tvbox.api.service.IMediaCatalogService +import com.muedsa.tvbox.demoplugin.helper.splitListBySize + +class MediaCatalogService( + private val danDanPlayApiService: DanDanPlayApiService +) : IMediaCatalogService { + + override suspend fun getConfig(): MediaCatalogConfig { + val resp = danDanPlayApiService.getSeasonYearMonth() + if (resp.errorCode != 0) { + throw RuntimeException(resp.errorMessage) + } + return MediaCatalogConfig( + initKey = "1", + pageSize = 20, + cardWidth = 210 / 2, + cardHeight = 302 / 2, + catalogOptions = buildList { + if (resp.seasons.isNotEmpty()) { + add( + MediaCatalogOption( + name = "年份", + value = "year", + items = resp.seasons.map{ it.year }.distinct().mapIndexed { index, year -> + MediaCatalogOptionItem( + name = year.toString(), + value = year.toString(), + defaultChecked = index == 0 + ) + }, + required = true + ) + ) + val firstMonth = resp.seasons.first().month + add( + MediaCatalogOption( + name = "月份", + value = "month", + items = buildList { + for (month in 1 .. 12) { + add( + MediaCatalogOptionItem( + name = month.toString(), + value = month.toString(), + defaultChecked = month == firstMonth + ) + ) + } + }, + required = true + ) + ) + + add( + MediaCatalogOption( + name = "Other", + value = "other", + items = buildList { + for (i in 0 .. 8) { + add( + MediaCatalogOptionItem( + name = "other$i", + value = i.toString(), + ) + ) + } + }, + multiple = true + ) + ) + } + } + ) + } + + override suspend fun catalog( + options: List, + loadKey: String, + loadSize: Int + ): PagingResult { + val pageIndex = loadKey.toInt() - 1 + val year = options.find { option -> option.value == "year" }?.items[0]?.value ?: throw RuntimeException("年份为必选项") + val month = options.find { option -> option.value == "month" }?.items[0]?.value ?: throw RuntimeException("月份为必选项") + val resp = danDanPlayApiService.getSeasonAnime(year, month) + if (resp.errorCode != 0) { + throw RuntimeException(resp.errorMessage) + } + val pages = splitListBySize(resp.bangumiList, loadSize) + println("${pages.size}") + pages.forEach { t -> println("${t.size}") } + return PagingResult( + list = if (pageIndex >= 0 && pageIndex < pages.size) { + pages[pageIndex].map { + MediaCard( + id = it.animeId.toString(), + title = it.animeTitle, + detailUrl = it.animeId.toString(), + coverImageUrl = it.imageUrl + ) + } + } else emptyList(), + nextKey = if (pageIndex + 1 < pages.size) "${pageIndex + 2}" else null, + prevKey = if (pageIndex - 1 >= 0) "$pageIndex" else null + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaDetailService.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaDetailService.kt new file mode 100644 index 0000000..8878137 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaDetailService.kt @@ -0,0 +1,61 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.api.data.DanmakuData +import com.muedsa.tvbox.api.data.DanmakuDataFlow +import com.muedsa.tvbox.api.data.MediaDetail +import com.muedsa.tvbox.api.data.MediaEpisode +import com.muedsa.tvbox.api.data.MediaHttpSource +import com.muedsa.tvbox.api.data.MediaPlaySource +import com.muedsa.tvbox.api.data.SavedMediaCard +import com.muedsa.tvbox.api.service.IMediaDetailService + +class MediaDetailService( + private val danDanPlayApiService: DanDanPlayApiService +) : IMediaDetailService { + + override suspend fun getDetailData(mediaId: String, detailUrl: String): MediaDetail { + val resp = danDanPlayApiService.getAnime(mediaId.toInt()) + if (resp.errorCode != 0) { + throw RuntimeException(resp.errorMessage) + } + val bangumi = resp.bangumi ?: throw RuntimeException("bangumi not found") + return MediaDetail( + id = bangumi.animeId.toString(), + title = bangumi.animeTitle, + subTitle = bangumi.typeDescription, + description = bangumi.summary, + detailUrl = bangumi.animeId.toString(), + backgroundImageUrl = bangumi.imageUrl, + playSourceList = listOf( + MediaPlaySource( + id = "bangumi", + name = "bangumi", + episodeList = bangumi.episodes.map { + MediaEpisode( + id = it.episodeId.toString(), + name = it.episodeTitle + ) + } + ) + ), + favoritedMediaCard = SavedMediaCard( + id = bangumi.animeId.toString(), + title = bangumi.animeTitle, + detailUrl = bangumi.animeId.toString(), + coverImageUrl = bangumi.imageUrl, + cardWidth = 210 / 2, + cardHeight = 302 / 2, + ) + ) + } + + override suspend fun getEpisodePlayInfo( + playSource: MediaPlaySource, + episode: MediaEpisode + ): MediaHttpSource = MediaHttpSource(url = "https://media.w3.org/2010/05/sintel/trailer.mp4") + + override suspend fun getEpisodeDanmakuDataList(episode: MediaEpisode): List + = emptyList() + + override suspend fun getEpisodeDanmakuDataFlow(episode: MediaEpisode): DanmakuDataFlow? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaSearchService.kt b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaSearchService.kt new file mode 100644 index 0000000..68ff917 --- /dev/null +++ b/app/src/main/java/com/muedsa/tvbox/demoplugin/service/MediaSearchService.kt @@ -0,0 +1,30 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.api.data.MediaCard +import com.muedsa.tvbox.api.data.MediaCardRow +import com.muedsa.tvbox.api.service.IMediaSearchService + +class MediaSearchService( + private val danDanPlayApiService: DanDanPlayApiService +) : IMediaSearchService { + override suspend fun searchMedias(query: String): MediaCardRow { + val resp = danDanPlayApiService.searchAnime(keyword = query) + if (resp.errorCode != 0) { + throw RuntimeException(resp.errorMessage) + } + return MediaCardRow( + title = "search list", + cardWidth = 210 / 2, + cardHeight = 302 / 2, + list = resp.animes?.map { + MediaCard( + id = it.animeId.toString(), + title = it.animeTitle, + detailUrl = it.animeId.toString(), + coverImageUrl = it.imageUrl, + subTitle = it.startOnlyDate + ) + } ?: emptyList() + ) + } +} \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..fe9cb27 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + TvBoxDemoPlugin + \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/Checker.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/Checker.kt new file mode 100644 index 0000000..928a6ab --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/Checker.kt @@ -0,0 +1,30 @@ +package com.muedsa.tvbox.demoplugin + +import com.muedsa.tvbox.api.data.MediaCard +import com.muedsa.tvbox.api.data.MediaCardRow +import com.muedsa.tvbox.api.data.MediaCardType + +fun checkMediaCardRows(rows: List) { + rows.forEach { checkMediaCardRow(it) } +} + +fun checkMediaCardRow(row: MediaCardRow) { + check(row.title.isNotEmpty()) + check(row.list.isNotEmpty()) + check(row.cardWidth > 0) + check(row.cardHeight > 0) + row.list.forEach { + checkMediaCard(card = it, cardType = row.cardType) + } +} + +fun checkMediaCard(card: MediaCard, cardType: MediaCardType) { + check(card.id.isNotEmpty()) + check(card.title.isNotEmpty()) + check(card.detailUrl.isNotEmpty()) + if (cardType != MediaCardType.NOT_IMAGE) { + check(card.coverImageUrl.isNotEmpty()) + } else { + check(card.backgroundColor > 0) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/FakePluginPrefStore.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/FakePluginPrefStore.kt new file mode 100644 index 0000000..d06171a --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/FakePluginPrefStore.kt @@ -0,0 +1,27 @@ +package com.muedsa.tvbox.demoplugin + +import com.muedsa.tvbox.api.store.IPluginPerfStore +import com.muedsa.tvbox.api.store.PluginPerfKey + +@Suppress("UNCHECKED_CAST") +class FakePluginPrefStore : IPluginPerfStore { + + private val store: MutableMap = mutableMapOf() + + override suspend fun get(key: PluginPerfKey): T? = + store[key.name] as T? + + override suspend fun getOrDefault(key: PluginPerfKey, default: T): T = + store[key.name] as T? ?: default + + override suspend fun filter(predicate: (String) -> Boolean): Map = + store.filter { predicate(it.key) } + + override suspend fun update(key: PluginPerfKey, value: T) { + store[key.name] = value as Any + } + + override suspend fun remove(key: PluginPerfKey) { + store.remove(key.name) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/PluginProvider.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/PluginProvider.kt new file mode 100644 index 0000000..047d473 --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/PluginProvider.kt @@ -0,0 +1,16 @@ +package com.muedsa.tvbox.demoplugin + +import com.muedsa.tvbox.api.plugin.TvBoxContext +import com.muedsa.tvbox.tool.IPv6Checker + +val TestPlugin by lazy { + DemoPlugin( + tvBoxContext = TvBoxContext( + screenWidth = 1920, + screenHeight = 1080, + debug = true, + store = FakePluginPrefStore(), + iPv6Status = IPv6Checker.checkIPv6Support() + ) + ) +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/PluginTest.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/PluginTest.kt new file mode 100644 index 0000000..2a686ad --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/PluginTest.kt @@ -0,0 +1,42 @@ +package com.muedsa.tvbox.demoplugin + +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PluginTest { + + @Test + fun create_test() { + TestPlugin + } + + @Test + fun onInit_test() = runTest { + TestPlugin.onInit() + } + + @Test + fun onLaunched_test() = runTest { + TestPlugin.onLaunched() + } + + @Test + fun provideMainScreenService_test() { + TestPlugin.provideMainScreenService() + } + + @Test + fun provideMediaDetailService_test() { + TestPlugin.provideMediaDetailService() + } + + @Test + fun provideMediaSearchService_test() { + TestPlugin.provideMediaSearchService() + } + + @Test + fun provideMediaCatalogService_test() { + TestPlugin.provideMediaCatalogService() + } +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MainScreenServiceTest.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MainScreenServiceTest.kt new file mode 100644 index 0000000..1cceaa2 --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MainScreenServiceTest.kt @@ -0,0 +1,18 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.demoplugin.TestPlugin +import com.muedsa.tvbox.demoplugin.checkMediaCardRows +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MainScreenServiceTest { + + private val service = TestPlugin.provideMainScreenService() + + @Test + fun getRowsDataTest() = runTest{ + val rows = service.getRowsData() + checkMediaCardRows(rows = rows) + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaCatalogServiceTest.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaCatalogServiceTest.kt new file mode 100644 index 0000000..0ec7d24 --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaCatalogServiceTest.kt @@ -0,0 +1,39 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.demoplugin.TestPlugin +import com.muedsa.tvbox.demoplugin.checkMediaCard +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MediaCatalogServiceTest { + + private val service = TestPlugin.provideMediaCatalogService() + + @Test + fun getConfig_test() = runTest { + val config = service.getConfig() + check(config.pageSize > 0) + check(config.catalogOptions.isNotEmpty()) + check(config.catalogOptions.size == config.catalogOptions.distinctBy { it.value }.size) + for (option in config.catalogOptions) { + check(option.items.isNotEmpty()) + check(option.items.size == option.items.distinctBy { it.value }.size) + } + check(config.cardWidth > 0) + } + + @Test + fun catalog_test() = runTest { + val config = service.getConfig() + val pagingResult = service.catalog( + options = config.catalogOptions, + loadKey = config.initKey, + loadSize = config.pageSize + ) + check(pagingResult.list.isNotEmpty()) + pagingResult.list.forEach { + checkMediaCard(it, config.cardType) + } + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaDetailServiceTest.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaDetailServiceTest.kt new file mode 100644 index 0000000..0898d4e --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaDetailServiceTest.kt @@ -0,0 +1,50 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.api.data.MediaCardType +import com.muedsa.tvbox.demoplugin.TestPlugin +import com.muedsa.tvbox.demoplugin.checkMediaCard +import com.muedsa.tvbox.demoplugin.checkMediaCardRow +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MediaDetailServiceTest { + + private val service = TestPlugin.provideMediaDetailService() + + @Test + fun getDetailData_test() = runTest{ + val detail = service.getDetailData("17998", "17998") + check(detail.id.isNotEmpty()) + check(detail.title.isNotEmpty()) + check(detail.detailUrl.isNotEmpty()) + check(detail.backgroundImageUrl.isNotEmpty()) + checkMediaCard(detail.favoritedMediaCard, cardType = MediaCardType.STANDARD) + check(detail.favoritedMediaCard.cardWidth > 0) + check(detail.favoritedMediaCard.cardHeight > 0) + check(detail.playSourceList.isNotEmpty()) + detail.playSourceList.forEach { mediaPlaySource -> + check(mediaPlaySource.id.isNotEmpty()) + check(mediaPlaySource.name.isNotEmpty()) + check(mediaPlaySource.episodeList.isNotEmpty()) + mediaPlaySource.episodeList.forEach { + check(it.id.isNotEmpty()) + check(it.name.isNotEmpty()) + } + } + detail.rows.forEach { + checkMediaCardRow(it) + } + } + + @Test + fun getEpisodePlayInfo_test() = runTest{ + val detail = service.getDetailData("17998", "17998") + check(detail.playSourceList.isNotEmpty()) + check(detail.playSourceList.flatMap { it.episodeList }.isNotEmpty()) + val mediaPlaySource = detail.playSourceList[0] + val mediaEpisode = mediaPlaySource.episodeList[0] + val playInfo = service.getEpisodePlayInfo(mediaPlaySource, mediaEpisode) + check(playInfo.url.isNotEmpty()) + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaSearchServiceTest.kt b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaSearchServiceTest.kt new file mode 100644 index 0000000..7592851 --- /dev/null +++ b/app/src/test/java/com/muedsa/tvbox/demoplugin/service/MediaSearchServiceTest.kt @@ -0,0 +1,17 @@ +package com.muedsa.tvbox.demoplugin.service + +import com.muedsa.tvbox.demoplugin.TestPlugin +import com.muedsa.tvbox.demoplugin.checkMediaCardRow +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class MediaSearchServiceTest { + + private val service = TestPlugin.provideMediaSearchService() + + @Test + fun searchMedias_test() = runTest { + val row = service.searchMedias("GIRLS BAND CRY") + checkMediaCardRow(row = row) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..26b575e --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,7 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.serialization) apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..20e2a01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# 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. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-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 +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..405f12d --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,15 @@ +[versions] +agp = "8.7.3" +kotlin = "2.1.0" +junit4 = "4.13.2" +kotlinxCoroutinesTest = "1.9.0" + +[libraries] +junit4 = { group = "junit", name = "junit", version.ref = "junit4" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..13d81c0 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +#Thu Oct 10 13:25:45 CST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..34fd4fa --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "TvBoxDemoPlugin" +include(":app") +include(":api") +project(":api").projectDir = rootDir.resolve("./TvBoxPlugin/api/") \ No newline at end of file