diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aeb3aac --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# AnimePlayerUA +Застосунок для зручного перегляду аніме для Android/Android TV (версії 9+). + +# Скріни +
+1. Головний екран + +![Скрін головного екрану](https://raw.github.com/dadencukillia/animeplayerua/main/screenshots/screenshot1.png) +
+
+2. Екран пошуку + +![Скрін екрану пошуку](https://raw.github.com/dadencukillia/animeplayerua/main/screenshots/screenshot2.png) +
+
+3. Екран аніме + +![Скрін екрану аніме](https://raw.github.com/dadencukillia/animeplayerua/main/screenshots/screenshot3.png) +
+
+4. Екран плеєру + +![Скрін екрану плеєру](https://raw.github.com/dadencukillia/animeplayerua/main/screenshots/screenshot4.png) +
+
+5. Екран аніме, додатковий функціонал + +![Скрін екрану аніме](https://raw.github.com/dadencukillia/animeplayerua/main/screenshots/screenshot5.png) +
+
+6. Екран особистого списку аніме + +![Скрін екрану особистого списку](https://raw.github.com/dadencukillia/animeplayerua/main/screenshots/screenshot6.png) +
+ +# Завантажити +Доступне для Android/Android TV **версії 9 або більше**. Завантажити APK файл можете натиснувши тут: [Завантажити .APK](https://github.com/dadencukillia/animeplayerua/releases) + +# Допомога (Contribution) +Допомога завжди вітається. Ви можете тестувати застосунок на баги і створювати [репорти](https://github.com/dadencukillia/animeplayerua/issues), [пропонувати ваші ідеї](https://github.com/dadencukillia/animeplayerua/discussions) або [допомогти оновлювати код](https://github.com/dadencukillia/animeplayerua/pulls). Застосунок розвивається і йому потрібна ваша допомога. + +Застосунок розроблений на мові програмування **Kotlin** з використанням **Jetpack Compose**. Серед залежностей такі бібліотеки: **room**, **exoplayer**, **navigation compose**, **ktor**, **kamel image**. + +Інформація береться (парситься) з сайту anitube у режимі реального часу. \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f9c8924..5f07548 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { applicationId = "com.crocoby.animeplayerua" minSdk = 28 targetSdk = 34 - versionCode = 2 - versionName = "0.1.1" + versionCode = 3 + versionName = "0.1.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -43,6 +43,7 @@ android { } buildFeatures { compose = true + buildConfig = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.1" diff --git a/app/src/main/java/com/crocoby/animeplayerua/App.kt b/app/src/main/java/com/crocoby/animeplayerua/App.kt index 51f98be..82e8961 100644 --- a/app/src/main/java/com/crocoby/animeplayerua/App.kt +++ b/app/src/main/java/com/crocoby/animeplayerua/App.kt @@ -2,11 +2,43 @@ package com.crocoby.animeplayerua import android.annotation.SuppressLint import android.content.Context +import android.util.Log import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.AlertDialogDefaults +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.whenCreated import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -18,17 +50,86 @@ import com.crocoby.animeplayerua.activities.SearchActivity import com.crocoby.animeplayerua.activities.VideoActivity import com.crocoby.animeplayerua.logic.AnimeDao import com.crocoby.animeplayerua.logic.AppDatabase +import com.crocoby.animeplayerua.logic.LatestAppVersionAndDownloadUrl +import com.crocoby.animeplayerua.logic.parser +import com.crocoby.animeplayerua.logic.runParser +import kotlinx.coroutines.launch var database: AnimeDao? = null @SuppressLint("StaticFieldLeak") var navController: NavHostController? = null +@OptIn(ExperimentalMaterial3Api::class) @Composable fun App(context: Context) { navController = rememberNavController() database = remember { AppDatabase.getDatabase(context).getDao() } + val coroutine = rememberCoroutineScope() + val uriHandler = LocalUriHandler.current + val appVersion = BuildConfig.VERSION_NAME + var updatePopupInfo by remember { mutableStateOf(null) } + var firstStart by rememberSaveable { mutableStateOf(true) } + + LifecycleEventEffect(Lifecycle.Event.ON_START) { + if (!firstStart) { + return@LifecycleEventEffect + } + firstStart = false + + coroutine.launch { + try { + val resp = parser.getLatestAppVersionAndDownloadUrl() + if (resp.version != appVersion) { + updatePopupInfo = resp + } + } catch (_: Exception) { } + } + } + MaterialTheme(colorScheme = darkScheme) { + if (updatePopupInfo != null) { + val curInfo = updatePopupInfo!! + + BasicAlertDialog( + onDismissRequest = { + updatePopupInfo = null + }, + ) { + Surface( + modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(16.dp), + shape = MaterialTheme.shapes.large, + tonalElevation = AlertDialogDefaults.TonalElevation + ) { + Column(modifier = Modifier.padding(16.dp)) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Доступна нова версія застосунку!", + style = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + ) + Text( + text = "Оновіть застосунок до версії ${curInfo.version}, щоб отримати нові функції та користуватися стабільнішою версією програми.", + ) + Button( + onClick = { + uriHandler.openUri(curInfo.downloadUrl) + updatePopupInfo = null + } + ) { + Text("Відвідати") + } + } + } + } + } + } + NavHost( navController!!, startDestination = Routes.HOME, diff --git a/app/src/main/java/com/crocoby/animeplayerua/MainActivity.kt b/app/src/main/java/com/crocoby/animeplayerua/MainActivity.kt index 871c10d..adeab78 100644 --- a/app/src/main/java/com/crocoby/animeplayerua/MainActivity.kt +++ b/app/src/main/java/com/crocoby/animeplayerua/MainActivity.kt @@ -1,6 +1,7 @@ package com.crocoby.animeplayerua import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge diff --git a/app/src/main/java/com/crocoby/animeplayerua/logic/Parser.kt b/app/src/main/java/com/crocoby/animeplayerua/logic/Parser.kt index b5bae6b..d24727e 100644 --- a/app/src/main/java/com/crocoby/animeplayerua/logic/Parser.kt +++ b/app/src/main/java/com/crocoby/animeplayerua/logic/Parser.kt @@ -42,6 +42,7 @@ fun runParser( } data class MainPageAnime(val bestSeason: List, val new: List) +data class LatestAppVersionAndDownloadUrl(val version: String, val downloadUrl: String) class Parser { private val client: HttpClient = HttpClient() @@ -236,4 +237,16 @@ class Parser { return fileUrl.substringBeforeLast("\"").substringAfterLast("\"").substringAfterLast(",").substringAfterLast("]") } + + suspend fun getLatestAppVersionAndDownloadUrl(): LatestAppVersionAndDownloadUrl { + val resp = client.get("https://raw.githubusercontent.com/dadencukillia/animeplayerua/main/releaseVersion") { + expectSuccess = true + + header("User-Agent", userAgent) + } + val body = resp.bodyAsText() + val split = body.split("\n") + + return LatestAppVersionAndDownloadUrl(split.first(), split.last()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/crocoby/animeplayerua/widgets/VideoPlayer.kt b/app/src/main/java/com/crocoby/animeplayerua/widgets/VideoPlayer.kt index 75b0a3e..c2af242 100644 --- a/app/src/main/java/com/crocoby/animeplayerua/widgets/VideoPlayer.kt +++ b/app/src/main/java/com/crocoby/animeplayerua/widgets/VideoPlayer.kt @@ -8,7 +8,10 @@ import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -50,6 +53,7 @@ fun VideoPlayer(modifier: Modifier, url: String) { val context = LocalContext.current.applicationContext val systemUiController = rememberSystemUiController() val focusRequester = remember { FocusRequester() } + var lastBarVisibleState by remember { mutableStateOf(false) } val lifecycleOwner = LocalLifecycleOwner.current val exoPlayer: ExoPlayer = remember { @@ -88,7 +92,12 @@ fun VideoPlayer(modifier: Modifier, url: String) { launch { while (true) { if (systemUiController.isSystemBarsVisible || systemUiController.isStatusBarVisible || systemUiController.isNavigationBarVisible) { - playerView.showController() + if (!lastBarVisibleState) { + lastBarVisibleState = true + playerView.showController() + } + } else { + lastBarVisibleState = false } delay(500) } @@ -140,7 +149,7 @@ fun VideoPlayer(modifier: Modifier, url: String) { DisposableEffect(Unit) { focusRequester.requestFocus() - val observer = LifecycleEventObserver { source, event -> + val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_PAUSE) { exoPlayer.pause() } diff --git a/gradle.properties b/gradle.properties index 20e2a01..414e4e8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,5 @@ 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 +android.nonTransitiveRClass=true +android.buildConfig=true \ No newline at end of file diff --git a/releaseVersion b/releaseVersion new file mode 100644 index 0000000..2ab74c6 --- /dev/null +++ b/releaseVersion @@ -0,0 +1,2 @@ +0.1.2 +https://github.com/dadencukillia/animeplayerua/releases/tag/v0.1.2 \ No newline at end of file diff --git a/screenshots/screenshot1.png b/screenshots/screenshot1.png new file mode 100644 index 0000000..9b60ae2 Binary files /dev/null and b/screenshots/screenshot1.png differ diff --git a/screenshots/screenshot2.png b/screenshots/screenshot2.png new file mode 100644 index 0000000..e579c7e Binary files /dev/null and b/screenshots/screenshot2.png differ diff --git a/screenshots/screenshot3.png b/screenshots/screenshot3.png new file mode 100644 index 0000000..28b93e2 Binary files /dev/null and b/screenshots/screenshot3.png differ diff --git a/screenshots/screenshot4.png b/screenshots/screenshot4.png new file mode 100644 index 0000000..31392b7 Binary files /dev/null and b/screenshots/screenshot4.png differ diff --git a/screenshots/screenshot5.png b/screenshots/screenshot5.png new file mode 100644 index 0000000..274ef3d Binary files /dev/null and b/screenshots/screenshot5.png differ diff --git a/screenshots/screenshot6.png b/screenshots/screenshot6.png new file mode 100644 index 0000000..5dcaca8 Binary files /dev/null and b/screenshots/screenshot6.png differ