├── composeApp ├── src │ ├── androidMain │ │ ├── res │ │ │ ├── resources.properties │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── xml │ │ │ │ └── locale_config.xml │ │ │ ├── values-v31 │ │ │ │ └── themes.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher_round.xml │ │ │ │ └── ic_launcher.xml │ │ │ └── drawable │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ └── ic_launcher_monochrome.xml │ │ ├── kotlin │ │ │ └── pw │ │ │ │ └── janyo │ │ │ │ └── whatanime │ │ │ │ ├── ui │ │ │ │ ├── screen │ │ │ │ │ └── MainScreen.android.kt │ │ │ │ ├── theme │ │ │ │ │ ├── NightMode.android.kt │ │ │ │ │ └── Theme.android.kt │ │ │ │ └── activity │ │ │ │ │ └── MainActivity.kt │ │ │ │ ├── module │ │ │ │ ├── DatabaseModule.android.kt │ │ │ │ ├── NetworkModule.android.kt │ │ │ │ └── PlatformModule.android.kt │ │ │ │ ├── utils │ │ │ │ ├── FileUtil.android.kt │ │ │ │ └── Util.android.kt │ │ │ │ ├── ApplicationExt.kt │ │ │ │ ├── Application.kt │ │ │ │ ├── Configure.android.kt │ │ │ │ └── base │ │ │ │ └── AppInfo.android.kt │ │ └── AndroidManifest.xml │ ├── commonMain │ │ ├── composeResources │ │ │ ├── drawable │ │ │ │ ├── janyo_studio.png │ │ │ │ ├── ic_app_store.xml │ │ │ │ ├── ic_google_play.xml │ │ │ │ ├── ic_app_icon.xml │ │ │ │ ├── ic_whatanime.xml │ │ │ │ ├── ic_github.xml │ │ │ │ └── ic_load_failed.xml │ │ │ ├── values-zh-rCN │ │ │ │ └── strings.xml │ │ │ ├── values-zh-rTW │ │ │ │ └── strings.xml │ │ │ └── values │ │ │ │ └── strings.xml │ │ └── kotlin │ │ │ └── pw │ │ │ └── janyo │ │ │ └── whatanime │ │ │ ├── module │ │ │ ├── PlatformModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ ├── Module.kt │ │ │ ├── ViewModelModule.kt │ │ │ ├── MediaModule.kt │ │ │ ├── NetworkModule.kt │ │ │ └── DatabaseModule.kt │ │ │ ├── model │ │ │ ├── DebugHttpInfo.kt │ │ │ ├── SearchQuota.kt │ │ │ ├── Animation.kt │ │ │ └── AnimationHistory.kt │ │ │ ├── Helper.kt │ │ │ ├── db │ │ │ ├── DB.kt │ │ │ ├── service │ │ │ │ ├── HistoryService.kt │ │ │ │ └── HistoryServiceImpl.kt │ │ │ └── dao │ │ │ │ └── HistoryDao.kt │ │ │ ├── base │ │ │ ├── AppInfo.kt │ │ │ └── ComposeViewModel.kt │ │ │ ├── utils │ │ │ ├── Util.kt │ │ │ ├── FileUtil.kt │ │ │ ├── EncryptUtil.kt │ │ │ ├── StringUtil.kt │ │ │ └── TimeUtil.kt │ │ │ ├── ui │ │ │ ├── theme │ │ │ │ ├── NightMode.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Icon.kt │ │ │ ├── components │ │ │ │ ├── DialogState.kt │ │ │ │ ├── VideoDialog.kt │ │ │ │ ├── MediaPlayer.kt │ │ │ │ ├── ShowProgressDialog.kt │ │ │ │ ├── NoDataLayout.kt │ │ │ │ ├── AppInfo.kt │ │ │ │ └── SwipeToDeleteContainer.kt │ │ │ ├── preference │ │ │ │ ├── Group.kt │ │ │ │ └── Settings.kt │ │ │ ├── navigation │ │ │ │ └── Nav.kt │ │ │ └── screen │ │ │ │ ├── AboutScreen.kt │ │ │ │ └── DetailScreen.kt │ │ │ ├── api │ │ │ └── SearchApi.kt │ │ │ ├── Configure.kt │ │ │ ├── viewmodel │ │ │ ├── HistoryViewModel.kt │ │ │ ├── DetailViewModel.kt │ │ │ ├── SettingsViewModel.kt │ │ │ └── MainViewModel.kt │ │ │ ├── App.kt │ │ │ └── repository │ │ │ └── AnimationRepository.kt │ └── iosMain │ │ └── kotlin │ │ └── pw │ │ └── janyo │ │ └── whatanime │ │ ├── MainViewController.kt │ │ ├── ui │ │ ├── screen │ │ │ └── MainScreen.ios.kt │ │ └── theme │ │ │ ├── NightMode.ios.kt │ │ │ └── Theme.ios.kt │ │ ├── module │ │ ├── NetworkModule.ios.kt │ │ ├── PlatformModule.ios.kt │ │ └── DatabaseModule.ios.kt │ │ ├── utils │ │ ├── FileUtil.ios.kt │ │ └── Util.ios.kt │ │ ├── Configure.ios.kt │ │ └── base │ │ └── AppInfo.ios.kt ├── proguard-rules.pro ├── dictionary.txt └── build.gradle.kts ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── iosApp ├── iosApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── app-icon-1024.jpg │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── iOSApp.swift │ ├── Info.plist │ └── ContentView.swift ├── iosApp.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── Configuration │ └── Config.xcconfig.template ├── CHANGELOG.md ├── gradle.properties ├── README.md ├── .gitignore ├── signing.gradle ├── settings.gradle.kts ├── .github └── workflows │ ├── release.yaml │ ├── build_android.yaml │ └── build_ios.yml ├── gradlew.bat └── gradlew /composeApp/src/androidMain/res/resources.properties: -------------------------------------------------------------------------------- 1 | unqualifiedResLocale=en-US -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WhatAnime 3 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 修复 2 | 3 | - 修复多语言选择界面只有英语的问题 4 | - 修复视频预览无法播放 5 | 6 | ### 优化 7 | 8 | - 优化文字选择体验,现在使用与平台相匹配的文字选择器 9 | - 优化检索结果交互体验 10 | - 将 Material3 升级到 Material Express 11 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/janyo_studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/composeApp/src/commonMain/composeResources/drawable/janyo_studio.png -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanYoStudio/WhatAnime/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.jpg -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/module/PlatformModule.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import org.koin.core.module.Module 4 | 5 | expect fun platformModule(): Module -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/MainViewController.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime 2 | 3 | import androidx.compose.ui.window.ComposeUIViewController 4 | 5 | fun MainViewController() = ComposeUIViewController { App() } -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/model/DebugHttpInfo.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.model 2 | 3 | data class DebugHttpInfo( 4 | val title: String, 5 | val datetime: String, 6 | val response: String, 7 | ) 8 | -------------------------------------------------------------------------------- /iosApp/Configuration/Config.xcconfig.template: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | 3 | PRODUCT_NAME=WhatAnime 4 | PRODUCT_BUNDLE_IDENTIFIER=pw.janyo.whatanime.WhatAnime$(TEAM_ID) 5 | 6 | CURRENT_PROJECT_VERSION={gitVersionCode} 7 | MARKETING_VERSION={appVersionName} -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/ui/screen/MainScreen.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.screen 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) { 7 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Kotlin 2 | kotlin.code.style=official 3 | kotlin.daemon.jvmargs=-Xmx3072M 4 | 5 | #Gradle 6 | org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 7 | 8 | #Android 9 | android.nonTransitiveRClass=true 10 | android.useAndroidX=true -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/Helper.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime 2 | 3 | import org.koin.core.context.startKoin 4 | import pw.janyo.whatanime.module.moduleList 5 | 6 | fun initKoin(){ 7 | startKoin { 8 | modules(moduleList()) 9 | } 10 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/module/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import org.koin.dsl.module 4 | import pw.janyo.whatanime.repository.AnimationRepository 5 | 6 | val repositoryModule = module { 7 | single { AnimationRepository() } 8 | } -------------------------------------------------------------------------------- /iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import ComposeApp 3 | 4 | @main 5 | struct iOSApp: App { 6 | init() { 7 | HelperKt.doInitKoin() 8 | } 9 | var body: some Scene { 10 | WindowGroup { 11 | ContentView() 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/xml/locale_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/ui/theme/NightMode.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.theme 2 | 3 | actual fun showNightModeSelectList(): List { 4 | val list = NightMode.entries.sortedBy { it.value }.toMutableList() 5 | list.remove(NightMode.MATERIAL_YOU) 6 | return list 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/ui/screen/MainScreen.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.screen 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) { 7 | androidx.activity.compose.BackHandler(enabled, onBack) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/module/Module.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import org.koin.core.module.Module 4 | 5 | fun moduleList(): List = 6 | listOf( 7 | platformModule(), 8 | databaseModule, 9 | networkModule, 10 | viewModelModule, 11 | repositoryModule, 12 | mediaModule, 13 | ) -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/model/SearchQuota.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class SearchQuota( 7 | val priority: Int = 0, 8 | val concurrency: Int = 0, 9 | val quota: Int = 0, 10 | val quotaUsed: Int = 0, 11 | ) { 12 | companion object { 13 | val EMPTY = SearchQuota() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/ui/theme/NightMode.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.theme 2 | 3 | import android.os.Build 4 | 5 | actual fun showNightModeSelectList(): List { 6 | val list = NightMode.entries.sortedBy { it.value }.toMutableList() 7 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 8 | list.remove(NightMode.MATERIAL_YOU) 9 | } 10 | return list 11 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleLocalizations 8 | 9 | en-US 10 | zh-CN 11 | zh-TW 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/module/NetworkModule.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import io.ktor.client.engine.HttpClientEngineConfig 4 | import io.ktor.client.engine.HttpClientEngineFactory 5 | import io.ktor.client.engine.darwin.Darwin 6 | 7 | actual fun httpClientEngine(): HttpClientEngineFactory = Darwin 8 | actual fun httpClientEngineConfig(config: HttpClientEngineConfig) { 9 | } 10 | 11 | actual fun userAgent(): String = "TODO" -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/module/PlatformModule.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import androidx.room.RoomDatabase 4 | import androidx.sqlite.driver.NativeSQLiteDriver 5 | import org.koin.core.module.Module 6 | import org.koin.dsl.module 7 | import pw.janyo.whatanime.db.AppDatabase 8 | 9 | actual fun platformModule(): Module = module { 10 | single> { 11 | getDatabaseBuilder() 12 | .setDriver(NativeSQLiteDriver()) 13 | } 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # “JanYo Studio” 授权解决方案(草案) 2 | 3 | ***为避免在其它非“JanYo Studio”通过书面授权认可的平台引发的授权 风险/争议,特拟此案。*** 4 | 5 | “JanYo Studio”(以下简称“工作室”)发布的产品缺省使用“GPL(GNU General Public License)”授权协议。工作室将在产品内部标识产品源程序及其附属品,使得任何人可以自由获取、阅读、传播,以及基于源代码重新构建它的二进制版本并发行该副本。 6 | 7 | 工作室希望使用者在自由使用我们的产品时,尊重工作室的劳动成果,承认工作室的知识产权。 8 | 9 | 工作室保证产品的自由使用、传播、阅读不受限,使用者应保障工作室的合法权益: 10 | - 针对开源资料,禁止删改原作者注明的版权、授权方式 等信息。 11 | - 源代码允许自由使用、添加至自己的项目,但必须注明来源,以及源的授权协议。 12 | - 在从事盈利、商用活动中,针对产品的传播复制需注明来源,以及源的授权协议。 13 | 14 | **针对产品授权、答疑可电邮至工作室官方电子邮箱 : mystery0dyl520@gmail.com** 15 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/module/DatabaseModule.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | 7 | import pw.janyo.whatanime.db.AppDatabase 8 | 9 | fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder { 10 | val appContext = ctx.applicationContext 11 | return Room.databaseBuilder( 12 | context = appContext, 13 | klass = AppDatabase::class.java, 14 | name = DATABASE_NAME 15 | ) 16 | } -------------------------------------------------------------------------------- /iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainViewControllerKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var body: some View { 15 | ComposeView() 16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/db/DB.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.db 2 | 3 | import androidx.room.ConstructedBy 4 | import androidx.room.Database 5 | import androidx.room.RoomDatabase 6 | import pw.janyo.whatanime.db.dao.HistoryDao 7 | import pw.janyo.whatanime.model.AnimationHistory 8 | import pw.janyo.whatanime.module.AppDatabaseConstructor 9 | 10 | @Database(entities = [(AnimationHistory::class)], version = 5) 11 | @ConstructedBy(AppDatabaseConstructor::class) 12 | abstract class AppDatabase : RoomDatabase() { 13 | abstract fun getHistoryDao(): HistoryDao 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .kotlin 3 | .gradle 4 | **/build/ 5 | xcuserdata 6 | !src/**/build/ 7 | composeApp/mapping.txt 8 | composeApp/seeds.txt 9 | composeApp/unused.txt 10 | composeApp/schemas/ 11 | composeApp/src/commonMain/composeResources/files/aboutlibraries.json 12 | local.properties 13 | .idea 14 | .DS_Store 15 | captures 16 | .externalNativeBuild 17 | .cxx 18 | *.xcodeproj/* 19 | !*.xcodeproj/project.pbxproj 20 | !*.xcodeproj/xcshareddata/ 21 | !*.xcodeproj/project.xcworkspace/ 22 | !*.xcworkspace/contents.xcworkspacedata 23 | **/xcshareddata/WorkspaceSettings.xcsettings 24 | /iosApp/Pods/ 25 | /iosApp/Configuration/Config.xcconfig 26 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/db/service/HistoryService.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.db.service 2 | 3 | import pw.janyo.whatanime.model.AnimationHistory 4 | 5 | interface HistoryService { 6 | suspend fun saveHistory(animationHistory: AnimationHistory): Long 7 | 8 | suspend fun getById(historyId: Int): AnimationHistory? 9 | 10 | suspend fun delete(historyId: Int): Int 11 | 12 | suspend fun queryAllHistory(): List 13 | 14 | suspend fun update(animationHistory: AnimationHistory): Int 15 | 16 | suspend fun queryHistoryByOriginPath(originPath: String): AnimationHistory? 17 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/base/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.base 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.painter.Painter 5 | import org.jetbrains.compose.resources.StringResource 6 | 7 | //设备id 8 | expect fun publicDeviceId(): String 9 | 10 | //应用名称 11 | expect fun appName(): String 12 | 13 | //应用包名 14 | expect fun packageName(): String 15 | 16 | //版本名称 17 | expect fun appVersionName(): String 18 | 19 | //版本号 20 | expect fun appVersionCode(): String 21 | expect fun appVersionCodeNumber(): Long 22 | 23 | expect fun getStoreUrl(): StringResource 24 | 25 | expect fun getStoreTitle(): StringResource 26 | 27 | @Composable 28 | expect fun getStoreIcon(): Painter -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/utils/Util.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import coil3.PlatformContext 4 | import multiplatform.network.cmptoast.showToast 5 | import org.jetbrains.compose.resources.getString 6 | import whatanime.composeapp.generated.resources.Res 7 | import whatanime.composeapp.generated.resources.hint_copy_done 8 | 9 | expect fun isOnline(): Boolean 10 | 11 | expect suspend fun copyToClipboard(context: PlatformContext, text: String) 12 | 13 | suspend fun copyToClipboardThenToast(context: PlatformContext, text: String) { 14 | copyToClipboard(context,text) 15 | showToast(getString(Res.string.hint_copy_done)) 16 | } 17 | 18 | expect fun showSharePanel(context: PlatformContext, shareText: String) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/utils/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import io.github.vinceglb.filekit.PlatformFile 4 | 5 | private val map = hashMapOf( 6 | "jpeg" to "image/jpeg", 7 | "jpg" to "image/jpeg", 8 | "png" to "image/png", 9 | "gif" to "image/gif", 10 | "bmp" to "image/bmp", 11 | "svg" to "image/svg+xml", 12 | "webp" to "image/webp", 13 | "tiff" to "image/tiff", 14 | "tif" to "image/tiff", 15 | "ico" to "image/vnd.microsoft.icon", 16 | "avif" to "image/avif", 17 | ) 18 | 19 | fun getMimeType(extension: String): String? = map[extension] 20 | 21 | expect suspend fun getCacheFile(file: PlatformFile): PlatformFile? 22 | 23 | expect fun getCacheFilePathBySavedCacheFilePath(path: String): String -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.activity 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import chaintech.videoplayer.util.PlaybackPreference 8 | import io.github.vinceglb.filekit.FileKit 9 | import io.github.vinceglb.filekit.dialogs.init 10 | import pw.janyo.whatanime.App 11 | 12 | class MainActivity : ComponentActivity() { 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | enableEdgeToEdge() 15 | super.onCreate(savedInstanceState) 16 | FileKit.init(this) 17 | PlaybackPreference.initialize(this) 18 | setContent { 19 | App() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/module/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import org.koin.core.module.dsl.viewModel 4 | import org.koin.dsl.module 5 | import pw.janyo.whatanime.base.ComposeViewModel 6 | import pw.janyo.whatanime.viewmodel.DetailViewModel 7 | import pw.janyo.whatanime.viewmodel.HistoryViewModel 8 | import pw.janyo.whatanime.viewmodel.MainViewModel 9 | import pw.janyo.whatanime.viewmodel.SettingsViewModel 10 | 11 | val viewModelModule = module { 12 | viewModel { 13 | object : ComposeViewModel() { 14 | init { 15 | launchToInit() 16 | } 17 | } 18 | } 19 | viewModel { MainViewModel() } 20 | viewModel { HistoryViewModel() } 21 | viewModel { DetailViewModel() } 22 | viewModel { SettingsViewModel() } 23 | } -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename": "app-icon-1024.jpg", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "dark" 14 | } 15 | ], 16 | "idiom" : "universal", 17 | "platform" : "ios", 18 | "size" : "1024x1024" 19 | }, 20 | { 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "tinted" 25 | } 26 | ], 27 | "idiom" : "universal", 28 | "platform" : "ios", 29 | "size" : "1024x1024" 30 | } 31 | ], 32 | "info" : { 33 | "author" : "xcode", 34 | "version" : 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /signing.gradle: -------------------------------------------------------------------------------- 1 | def kFile = rootProject.file('local.properties') 2 | def props = new Properties() 3 | if (kFile.canRead()) { 4 | props.load(new FileInputStream(kFile)) 5 | 6 | if (props != null) { 7 | android.signingConfigs.sign.storeFile file(props['SIGN_KEY_STORE_FILE']) 8 | android.signingConfigs.sign.storePassword props['SIGN_KEY_STORE_PASSWORD'] 9 | android.signingConfigs.sign.keyAlias props['SIGN_KEY_ALIAS'] 10 | android.signingConfigs.sign.keyPassword props['SIGN_KEY_PASSWORD'] 11 | } 12 | } else { 13 | android.signingConfigs.sign.storeFile file(System.getenv('SIGN_KEY_STORE_FILE')) 14 | android.signingConfigs.sign.storePassword System.getenv('SIGN_KEY_STORE_PASSWORD') 15 | android.signingConfigs.sign.keyAlias System.getenv('SIGN_KEY_ALIAS') 16 | android.signingConfigs.sign.keyPassword System.getenv('SIGN_KEY_PASSWORD') 17 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/utils/FileUtil.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import io.github.vinceglb.filekit.PlatformFile 4 | import io.github.vinceglb.filekit.absolutePath 5 | import pw.janyo.whatanime.context 6 | import java.io.File 7 | 8 | private const val CACHE_IMAGE_FILE_NAME = "cacheImage" 9 | 10 | actual suspend fun getCacheFile(file: PlatformFile): PlatformFile? { 11 | val saveParent = context.getExternalFilesDir(CACHE_IMAGE_FILE_NAME) ?: return null 12 | if (!saveParent.exists()) 13 | saveParent.mkdirs() 14 | if (saveParent.isDirectory || saveParent.delete() && saveParent.mkdirs()) { 15 | val md5Name = file.absolutePath().md5() 16 | return PlatformFile(File(saveParent, md5Name)) 17 | } 18 | return null 19 | } 20 | 21 | actual fun getCacheFilePathBySavedCacheFilePath(path: String): String = path -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/module/NetworkModule.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import android.webkit.WebSettings 4 | import io.ktor.client.engine.HttpClientEngineConfig 5 | import io.ktor.client.engine.HttpClientEngineFactory 6 | import io.ktor.client.engine.okhttp.OkHttp 7 | import io.ktor.client.engine.okhttp.OkHttpConfig 8 | import pw.janyo.whatanime.context 9 | import java.util.concurrent.TimeUnit 10 | 11 | actual fun httpClientEngine(): HttpClientEngineFactory = OkHttp 12 | 13 | actual fun httpClientEngineConfig(config: HttpClientEngineConfig) { 14 | val okHttpConfig = config as OkHttpConfig 15 | okHttpConfig.config { 16 | connectTimeout(40, TimeUnit.SECONDS) 17 | readTimeout(40, TimeUnit.SECONDS) 18 | } 19 | } 20 | 21 | actual fun userAgent(): String = WebSettings.getDefaultUserAgent(context) -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "WhatAnime" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | google { 7 | mavenContent { 8 | includeGroupAndSubgroups("androidx") 9 | includeGroupAndSubgroups("com.android") 10 | includeGroupAndSubgroups("com.google") 11 | } 12 | } 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | 18 | dependencyResolutionManagement { 19 | repositories { 20 | google { 21 | mavenContent { 22 | includeGroupAndSubgroups("androidx") 23 | includeGroupAndSubgroups("com.android") 24 | includeGroupAndSubgroups("com.google") 25 | } 26 | } 27 | mavenCentral() 28 | } 29 | } 30 | 31 | include(":composeApp") -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/ApplicationExt.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.provider.Settings 6 | 7 | @SuppressLint("StaticFieldLeak") 8 | internal lateinit var context: Context 9 | 10 | //设备id 11 | val publicDeviceId: String 12 | @SuppressLint("HardwareIds") 13 | get() = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) 14 | 15 | //应用名称 16 | val appName: String 17 | get() = context.getString(R.string.app_name) 18 | 19 | //应用包名 20 | const val packageName: String = BuildConfig.APPLICATION_ID 21 | 22 | //版本名称 23 | const val appVersionName: String = BuildConfig.VERSION_NAME 24 | 25 | //版本号 26 | const val appVersionCode: String = BuildConfig.VERSION_CODE.toString() 27 | const val appVersionCodeNumber: Long = BuildConfig.VERSION_CODE.toLong() 28 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/module/PlatformModule.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import android.content.ClipboardManager 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import androidx.room.RoomDatabase 7 | import androidx.sqlite.driver.AndroidSQLiteDriver 8 | import org.koin.android.ext.koin.androidContext 9 | import org.koin.core.module.Module 10 | import org.koin.dsl.module 11 | import pw.janyo.whatanime.db.AppDatabase 12 | 13 | actual fun platformModule(): Module = module { 14 | single { androidContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } 15 | single { androidContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } 16 | 17 | single> { 18 | getDatabaseBuilder(get()) 19 | .setDriver(AndroidSQLiteDriver()) 20 | } 21 | } -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/ui/theme/Theme.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.ColorScheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.collectAsState 7 | import androidx.compose.runtime.getValue 8 | 9 | @Composable 10 | actual fun getColorScheme(): ColorScheme { 11 | val mode by Theme.nightMode.collectAsState() 12 | val isSystemInDarkTheme = isSystemInDarkTheme() 13 | return when (mode) { 14 | //强制开启夜间模式 15 | NightMode.ON -> DarkColorScheme 16 | //强制关闭夜间模式 17 | NightMode.OFF -> LightColorScheme 18 | 19 | else -> { 20 | if (isSystemInDarkTheme) { 21 | DarkColorScheme 22 | } else { 23 | LightColorScheme 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/ui/theme/NightMode.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.theme 2 | 3 | import org.jetbrains.compose.resources.StringResource 4 | import whatanime.composeapp.generated.resources.Res 5 | import whatanime.composeapp.generated.resources.array_night_mode_always_off 6 | import whatanime.composeapp.generated.resources.array_night_mode_always_on 7 | import whatanime.composeapp.generated.resources.array_night_mode_auto 8 | import whatanime.composeapp.generated.resources.array_night_mode_material_you 9 | 10 | enum class NightMode( 11 | val value: Int, 12 | val title: StringResource, 13 | ) { 14 | AUTO(0, Res.string.array_night_mode_auto), 15 | ON(1, Res.string.array_night_mode_always_on), 16 | OFF(2, Res.string.array_night_mode_always_off), 17 | MATERIAL_YOU(3, Res.string.array_night_mode_material_you), 18 | } 19 | 20 | expect fun showNightModeSelectList(): List -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/base/ComposeViewModel.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.base 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import kotlinx.coroutines.launch 6 | import org.jetbrains.compose.resources.StringResource 7 | import org.jetbrains.compose.resources.getString 8 | import org.koin.core.component.KoinComponent 9 | import whatanime.composeapp.generated.resources.Res 10 | import whatanime.composeapp.generated.resources.allStringResources 11 | 12 | abstract class ComposeViewModel : ViewModel(), KoinComponent { 13 | fun launchToInit() { 14 | viewModelScope.launch { 15 | Res.allStringResources.forEach { 16 | stringResourceMap[it.value] = getString(it.value) 17 | } 18 | } 19 | } 20 | 21 | protected fun StringResource.string(): String = stringResourceMap[this]!! 22 | 23 | companion object { 24 | private val stringResourceMap = hashMapOf() 25 | } 26 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/Application.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime 2 | 3 | import android.app.Application 4 | import com.tencent.mmkv.MMKV 5 | import multiplatform.network.cmptoast.AppContext 6 | import org.koin.android.ext.koin.androidContext 7 | import org.koin.android.ext.koin.androidLogger 8 | import org.koin.core.context.startKoin 9 | import org.koin.core.logger.Level 10 | import pw.janyo.whatanime.module.moduleList 11 | import pw.janyo.whatanime.BuildConfig 12 | 13 | class Application : Application() { 14 | override fun onCreate() { 15 | super.onCreate() 16 | context = this 17 | startKoin { 18 | androidLogger(Level.ERROR) 19 | androidContext(this@Application) 20 | modules(moduleList()) 21 | } 22 | AppContext.apply { set(applicationContext) } 23 | MMKV.initialize(this) 24 | Configure.lastVersion = BuildConfig.VERSION_CODE 25 | //每次启动都禁用调试模式 26 | Configure.debugMode = BuildConfig.DEBUG 27 | } 28 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_app_store.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/module/DatabaseModule.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import androidx.room.Room 4 | import androidx.room.RoomDatabase 5 | import kotlinx.cinterop.ExperimentalForeignApi 6 | import platform.Foundation.NSDocumentDirectory 7 | import platform.Foundation.NSFileManager 8 | import platform.Foundation.NSUserDomainMask 9 | import pw.janyo.whatanime.db.AppDatabase 10 | 11 | fun getDatabaseBuilder(): RoomDatabase.Builder { 12 | val dbFilePath = documentDirectory() + "/" + DATABASE_NAME + ".db" 13 | return Room.databaseBuilder( 14 | name = dbFilePath, 15 | ) 16 | } 17 | 18 | @OptIn(ExperimentalForeignApi::class) 19 | private fun documentDirectory(): String { 20 | val documentDirectory = NSFileManager.defaultManager.URLForDirectory( 21 | directory = NSDocumentDirectory, 22 | inDomain = NSUserDomainMask, 23 | appropriateForURL = null, 24 | create = false, 25 | error = null, 26 | ) 27 | return requireNotNull(documentDirectory?.path) 28 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/Configure.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime 2 | 3 | import com.tencent.mmkv.MMKV 4 | 5 | val kv = MMKV.mmkvWithID("configure") 6 | 7 | actual inline fun getConfiguration(key: String, defaultValue: T): T = 8 | when (defaultValue) { 9 | is Boolean -> kv.decodeBool(key, defaultValue) 10 | is Int -> kv.decodeInt(key, defaultValue) 11 | is Long -> kv.decodeLong(key, defaultValue) 12 | is Float -> kv.decodeFloat(key, defaultValue) 13 | is String -> kv.decodeString(key, defaultValue) 14 | else -> throw IllegalArgumentException("Unsupported type") 15 | } as T 16 | 17 | actual inline fun setConfiguration(key: String, value: T) { 18 | when(value){ 19 | is Boolean -> kv.encode(key, value) 20 | is Int -> kv.encode(key, value) 21 | is Long -> kv.encode(key, value) 22 | is Float -> kv.encode(key, value) 23 | is String -> kv.encode(key, value) 24 | else -> throw IllegalArgumentException("Unsupported type") 25 | } 26 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/db/dao/HistoryDao.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import androidx.room.Update 7 | import pw.janyo.whatanime.model.AnimationHistory 8 | 9 | @Dao 10 | interface HistoryDao { 11 | @Insert 12 | suspend fun saveHistory(animationHistory: AnimationHistory): Long 13 | 14 | @Query("SELECT * FROM tb_animation_history where id = :historyId LIMIT 1") 15 | suspend fun getById(historyId: Int): AnimationHistory? 16 | 17 | @Query("DELETE FROM tb_animation_history where id = :historyId") 18 | suspend fun delete(historyId: Int): Int 19 | 20 | @Query("SELECT * FROM tb_animation_history") 21 | suspend fun queryAllHistory(): List 22 | 23 | @Update 24 | suspend fun update(animationHistory: AnimationHistory): Int 25 | 26 | @Query("SELECT * FROM tb_animation_history WHERE origin_path = :originPath LIMIT 1") 27 | suspend fun queryHistoryByOriginPath(originPath: String): AnimationHistory? 28 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.theme 2 | 3 | import androidx.compose.material3.ColorScheme 4 | import androidx.compose.material3.MaterialExpressiveTheme 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.darkColorScheme 7 | import androidx.compose.material3.lightColorScheme 8 | import androidx.compose.runtime.Composable 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import pw.janyo.whatanime.Configure 11 | 12 | val DarkColorScheme = darkColorScheme() 13 | 14 | val LightColorScheme = lightColorScheme() 15 | 16 | @Composable 17 | expect fun getColorScheme(): ColorScheme 18 | 19 | @Composable 20 | fun WhatAnimeTheme( 21 | content: @Composable() () -> Unit 22 | ) { 23 | val colorScheme = getColorScheme() 24 | 25 | MaterialExpressiveTheme( 26 | typography = MaterialTheme.typography, 27 | colorScheme = colorScheme, 28 | content = content, 29 | ) 30 | } 31 | 32 | object Theme { 33 | val nightMode = MutableStateFlow(Configure.nightMode) 34 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/model/Animation.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.json.JsonElement 6 | 7 | @Serializable 8 | data class SearchAnimeResult( 9 | val error: String = "", 10 | val frameCount: Long = 0, 11 | val result: List, 12 | ) 13 | 14 | @Serializable 15 | data class SearchAnimeResultItem( 16 | @SerialName("anilist") 17 | val aniList: SearchAniListResult, 18 | @SerialName("filename") 19 | val fileName: String, 20 | val episode: JsonElement? = null, 21 | val from: Double? = 0.0, 22 | val to: Double? = 0.0, 23 | val similarity: Double = 0.0, 24 | val video: String, 25 | val image: String, 26 | ) 27 | 28 | @Serializable 29 | data class SearchAniListResult( 30 | val id: Long? = 0, 31 | val idMal: Long? = 0, 32 | val title: AniListTitleResult, 33 | @SerialName("isAdult") 34 | val adult: Boolean = false, 35 | ) 36 | 37 | @Serializable 38 | data class AniListTitleResult( 39 | val native: String? = "", 40 | ) -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/utils/FileUtil.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import io.github.vinceglb.filekit.FileKit 4 | import io.github.vinceglb.filekit.PlatformFile 5 | import io.github.vinceglb.filekit.absolutePath 6 | import io.github.vinceglb.filekit.createDirectories 7 | import io.github.vinceglb.filekit.exists 8 | import io.github.vinceglb.filekit.filesDir 9 | import kotlinx.cinterop.ExperimentalForeignApi 10 | 11 | private const val CACHE_IMAGE_FILE_NAME = "cacheImage" 12 | 13 | @OptIn(ExperimentalForeignApi::class) 14 | actual suspend fun getCacheFile(file: PlatformFile): PlatformFile? { 15 | val dir = PlatformFile(FileKit.filesDir, CACHE_IMAGE_FILE_NAME) 16 | if (!dir.exists()) { 17 | dir.createDirectories() 18 | } 19 | val originalFilePath = file.absolutePath() 20 | val md5Name = originalFilePath.md5() 21 | return PlatformFile(dir, md5Name) 22 | } 23 | 24 | actual fun getCacheFilePathBySavedCacheFilePath(path: String): String { 25 | val dir = PlatformFile(FileKit.filesDir, CACHE_IMAGE_FILE_NAME) 26 | val fileName = path.substringAfterLast("/") 27 | return PlatformFile(dir, fileName).absolutePath() 28 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/db/service/HistoryServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.db.service 2 | 3 | import org.koin.core.component.KoinComponent 4 | import org.koin.core.component.inject 5 | import pw.janyo.whatanime.db.dao.HistoryDao 6 | import pw.janyo.whatanime.model.AnimationHistory 7 | 8 | class HistoryServiceImpl : HistoryService, KoinComponent { 9 | private val historyDao: HistoryDao by inject() 10 | 11 | override suspend fun saveHistory(animationHistory: AnimationHistory): Long = 12 | historyDao.saveHistory(animationHistory) 13 | 14 | override suspend fun getById(historyId: Int): AnimationHistory? = historyDao.getById(historyId) 15 | 16 | override suspend fun delete(historyId: Int): Int = 17 | historyDao.delete(historyId) 18 | 19 | override suspend fun queryAllHistory(): List = historyDao.queryAllHistory() 20 | 21 | override suspend fun update(animationHistory: AnimationHistory): Int = 22 | historyDao.update(animationHistory) 23 | 24 | override suspend fun queryHistoryByOriginPath(originPath: String): AnimationHistory? = 25 | historyDao.queryHistoryByOriginPath(originPath) 26 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/ui/components/DialogState.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.saveable.rememberSaveable 7 | import androidx.compose.runtime.setValue 8 | 9 | class ShowDialogState( 10 | initialValue: Boolean, 11 | ) { 12 | internal var show by mutableStateOf(initialValue) 13 | 14 | val isShowing: Boolean 15 | get() = show 16 | 17 | fun show() { 18 | show = true 19 | } 20 | 21 | fun hide() { 22 | show = false 23 | } 24 | 25 | companion object { 26 | fun Saver() = androidx.compose.runtime.saveable.Saver( 27 | save = { it.show }, 28 | restore = { ShowDialogState(it) } 29 | ) 30 | } 31 | } 32 | 33 | @Composable 34 | fun rememberShowDialogState( 35 | initialValue: Boolean = false, 36 | ): ShowDialogState { 37 | return rememberSaveable( 38 | saver = ShowDialogState.Saver() 39 | ) { 40 | ShowDialogState(initialValue) 41 | } 42 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/module/MediaModule.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.module 2 | 3 | import chaintech.videoplayer.host.MediaPlayerEvent 4 | import chaintech.videoplayer.host.MediaPlayerHost 5 | import chaintech.videoplayer.model.ScreenResize 6 | import org.koin.dsl.module 7 | import pw.janyo.whatanime.ui.components.PlayerState 8 | 9 | val mediaModule = module { 10 | single { 11 | MediaPlayerHost( 12 | isPaused = true, 13 | isLooping = false, 14 | isFullScreen = false, 15 | initialVideoFitMode = ScreenResize.FIT, 16 | ) 17 | } 18 | single { 19 | val mediaPlayerHost = get() 20 | val playerState = PlayerState() 21 | mediaPlayerHost.onEvent = { event -> 22 | when (event) { 23 | is MediaPlayerEvent.PauseChange -> { 24 | playerState.isPaused.value = event.isPaused 25 | } 26 | 27 | MediaPlayerEvent.MediaEnd -> { 28 | playerState.playEnd() 29 | } 30 | 31 | else -> {} 32 | } 33 | } 34 | playerState 35 | } 36 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/ui/preference/Group.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.preference 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.ColumnScope 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import me.zhanghai.compose.preference.PreferenceCategory 10 | import me.zhanghai.compose.preference.ProvidePreferenceTheme 11 | import me.zhanghai.compose.preference.preferenceTheme 12 | 13 | @Composable 14 | fun SettingsGroup( 15 | title: @Composable () -> Unit, 16 | modifier: Modifier = Modifier, 17 | content: @Composable ColumnScope.() -> Unit, 18 | ) { 19 | ProvidePreferenceTheme( 20 | theme = myPreferenceTheme(), 21 | ) { 22 | PreferenceCategory( 23 | title = title, 24 | ) 25 | Column( 26 | modifier = modifier.fillMaxWidth(), 27 | content = content, 28 | ) 29 | } 30 | } 31 | 32 | @Composable 33 | private fun myPreferenceTheme() = preferenceTheme( 34 | summaryColor = MaterialTheme.colorScheme.outline, 35 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_google_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/utils/EncryptUtil.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import io.github.vinceglb.filekit.PlatformFile 4 | import io.github.vinceglb.filekit.readBytes 5 | import org.kotlincrypto.hash.md.MD5 6 | import org.kotlincrypto.hash.sha1.SHA1 7 | import org.kotlincrypto.hash.sha2.SHA256 8 | import org.kotlincrypto.hash.sha2.SHA384 9 | import org.kotlincrypto.hash.sha2.SHA512 10 | 11 | @OptIn(ExperimentalStdlibApi::class) 12 | fun String.md5(): String = MD5().digest(encodeToByteArray()).toHexString() 13 | 14 | @OptIn(ExperimentalStdlibApi::class) 15 | fun String.sha1(): String = SHA1().digest(encodeToByteArray()).toHexString() 16 | 17 | @OptIn(ExperimentalStdlibApi::class) 18 | fun String.sha256(): String = SHA256().digest(encodeToByteArray()).toHexString() 19 | 20 | @OptIn(ExperimentalStdlibApi::class) 21 | fun String.sha512(): String = SHA512().digest(encodeToByteArray()).toHexString() 22 | 23 | @OptIn(ExperimentalStdlibApi::class) 24 | fun String.sha384(): String = SHA384().digest(encodeToByteArray()).toHexString() 25 | 26 | @OptIn(ExperimentalStdlibApi::class) 27 | suspend fun PlatformFile.md5(): String { 28 | val digest = MD5() 29 | digest.update(readBytes()) 30 | val bytes = digest.digest() 31 | return bytes.toHexString() 32 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/utils/StringUtil.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import kotlinx.serialization.json.JsonArray 4 | import kotlinx.serialization.json.JsonElement 5 | import kotlinx.serialization.json.JsonNull 6 | import kotlinx.serialization.json.JsonPrimitive 7 | import kotlin.math.pow 8 | import kotlin.math.roundToLong 9 | 10 | fun formatDecimal(value: Double, digits: Int = 1): String { 11 | val multiplier = 10.0.pow(digits + 1) 12 | val rounded = (value * multiplier).roundToLong() / multiplier 13 | return rounded.toString() 14 | } 15 | 16 | fun formatEpisode(episodeElement: JsonElement?): String? { 17 | return when (episodeElement) { 18 | null, is JsonNull -> null 19 | is JsonPrimitive -> { 20 | if (episodeElement.isString) { 21 | episodeElement.content 22 | } else { 23 | episodeElement.content 24 | } 25 | } 26 | is JsonArray -> { 27 | episodeElement.joinToString(", ") { 28 | if (it is JsonPrimitive) { 29 | it.content 30 | } else { 31 | "" 32 | } 33 | }.ifEmpty { null } 34 | } 35 | else -> null 36 | } 37 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/pw/janyo/whatanime/utils/Util.android.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.utils 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Intent 6 | import android.net.ConnectivityManager 7 | import coil3.PlatformContext 8 | import org.koin.java.KoinJavaComponent 9 | import pw.janyo.whatanime.appName 10 | 11 | @Suppress("DEPRECATION") 12 | actual fun isOnline(): Boolean { 13 | val connectivityManager = 14 | KoinJavaComponent.get(ConnectivityManager::class.java) 15 | val networkInfo = connectivityManager.activeNetworkInfo 16 | return networkInfo?.isConnected == true 17 | } 18 | 19 | actual suspend fun copyToClipboard(context: PlatformContext, text: String) { 20 | val clipboardManager = 21 | KoinJavaComponent.get(ClipboardManager::class.java) 22 | val clipData = ClipData.newPlainText(appName, text) 23 | clipboardManager.setPrimaryClip(clipData) 24 | } 25 | 26 | actual fun showSharePanel(context: PlatformContext, shareText: String) { 27 | val shareIntent = Intent(Intent.ACTION_SEND).apply { 28 | putExtra(Intent.EXTRA_TEXT, shareText) 29 | type = "text/plain" 30 | } 31 | context.startActivity(Intent.createChooser(shareIntent, "")) 32 | } -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/pw/janyo/whatanime/Configure.ios.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime 2 | 3 | import com.russhwolf.settings.NSUserDefaultsSettings 4 | import com.russhwolf.settings.Settings 5 | import com.russhwolf.settings.set 6 | import platform.Foundation.NSUserDefaults 7 | 8 | val delegate: NSUserDefaults = NSUserDefaults.standardUserDefaults() 9 | val settings: Settings = NSUserDefaultsSettings(delegate) 10 | 11 | actual inline fun getConfiguration(key: String, defaultValue: T): T = 12 | when (defaultValue) { 13 | is Boolean -> settings.getBoolean(key, defaultValue) 14 | is Int -> settings.getInt(key, defaultValue) 15 | is Long -> settings.getLong(key, defaultValue) 16 | is Float -> settings.getFloat(key, defaultValue) 17 | is String -> settings.getString(key, defaultValue) 18 | else -> throw IllegalArgumentException("Unsupported type") 19 | } as T 20 | 21 | actual inline fun setConfiguration(key: String, value: T) { 22 | when(value){ 23 | is Boolean -> settings[key] = value 24 | is Int -> settings[key] = value 25 | is Long -> settings[key] = value 26 | is Float -> settings[key] = value 27 | is String -> settings[key] = value 28 | else -> throw IllegalArgumentException("Unsupported type") 29 | } 30 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/pw/janyo/whatanime/ui/components/VideoDialog.kt: -------------------------------------------------------------------------------- 1 | package pw.janyo.whatanime.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.width 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.collectAsState 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.window.Dialog 13 | import chaintech.videoplayer.host.MediaPlayerHost 14 | import org.koin.compose.koinInject 15 | 16 | @Composable 17 | fun BuildVideoDialog() { 18 | val playerState = koinInject() 19 | val mediaPlayerHost = koinInject() 20 | val isLoadUrl by playerState.isLoadUrl.collectAsState() 21 | if (!isLoadUrl) { 22 | return 23 | } 24 | Dialog(onDismissRequest = { 25 | mediaPlayerHost.pause() 26 | playerState.release() 27 | }) { 28 | Box(modifier = Modifier.padding(4.dp)) { 29 | PlatformMediaPlayerView( 30 | modifier = Modifier 31 | .width(480.dp) 32 | .height(270.dp), 33 | mediaPlayerHost, 34 | ) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 14 | 15 | 16 | 27 | 28 |