diff --git a/.gitignore b/.gitignore
index 6ca2a82..83dd92d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
*.iml
*.jks
*.aab
+*.aar
*.base64
*.json
+*/libs
.gradle
/local.properties
.idea
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2a054fa..50fe2f5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -8,7 +8,7 @@ plugins {
alias(libs.plugins.gradle.ktlint)
}
-val properties = Properties()
+val properties: Properties = Properties()
val propertiesFile = rootProject.file("local.properties")
if (propertiesFile.exists()) {
@@ -116,6 +116,7 @@ android {
}
dependencies {
+ // implementation(fileTree("dir" to "./libs", "include" to arrayOf("*.aar")))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
diff --git a/app/src/main/java/com/infbyte/amuzeo/models/AmuzeoSideEffect.kt b/app/src/main/java/com/infbyte/amuzeo/models/AmuzeoSideEffect.kt
index b866954..86d4c07 100644
--- a/app/src/main/java/com/infbyte/amuzeo/models/AmuzeoSideEffect.kt
+++ b/app/src/main/java/com/infbyte/amuzeo/models/AmuzeoSideEffect.kt
@@ -2,4 +2,5 @@ package com.infbyte.amuzeo.models
data class AmuzeoSideEffect(
val showSplash: Boolean = true,
+ val showAppSettingsDialog: Boolean = false,
)
diff --git a/app/src/main/java/com/infbyte/amuzeo/presentation/ui/activities/MainActivity.kt b/app/src/main/java/com/infbyte/amuzeo/presentation/ui/activities/MainActivity.kt
index afdfc56..84c285c 100644
--- a/app/src/main/java/com/infbyte/amuzeo/presentation/ui/activities/MainActivity.kt
+++ b/app/src/main/java/com/infbyte/amuzeo/presentation/ui/activities/MainActivity.kt
@@ -25,6 +25,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.gms.ads.MobileAds
import com.infbyte.amuze.ads.GoogleMobileAdsConsentManager
+import com.infbyte.amuze.contracts.AppSettingsContract
+import com.infbyte.amuze.ui.dialogs.AppSettingsRedirectDialog
import com.infbyte.amuze.ui.screens.AboutScreen
import com.infbyte.amuze.ui.screens.LoadingScreen
import com.infbyte.amuze.ui.screens.NoMediaAvailableScreen
@@ -38,6 +40,7 @@ import com.infbyte.amuzeo.presentation.ui.screens.VideoScreen
import com.infbyte.amuzeo.presentation.ui.screens.VideosScreen
import com.infbyte.amuzeo.presentation.viewmodels.VideosViewModel
import com.infbyte.amuzeo.utils.AmuzeoPermissions.isReadPermissionGranted
+import com.infbyte.amuzeo.utils.AmuzeoPermissions.showReqPermRationale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -60,6 +63,14 @@ class MainActivity : ComponentActivity() {
}
}
+ private val appSettingsLauncher =
+ registerForActivityResult(AppSettingsContract()) {
+ videosViewModel.setReadPermGranted(it)
+ if (it) {
+ videosViewModel.init(this)
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
@@ -104,6 +115,17 @@ class MainActivity : ComponentActivity() {
return@Surface
}
+ if (videosViewModel.sideEffect.showAppSettingsDialog) {
+ AppSettingsRedirectDialog(
+ stringResource(R.string.amuzeo_perm_rationale),
+ onAccept = {
+ videosViewModel.hideAppSettingsRedirectDialog()
+ appSettingsLauncher.launch(packageName)
+ },
+ onDismiss = { videosViewModel.hideAppSettingsRedirectDialog() },
+ )
+ }
+
initialScreen =
when {
!videosViewModel.state.isReadPermGranted -> {
@@ -143,7 +165,13 @@ class MainActivity : ComponentActivity() {
NoMediaPermissionScreen(
appIcon = R.drawable.amuzeo_intro,
action = R.string.amuzeo_watch,
- onStartAction = { launchPermRequest() },
+ onStartAction = {
+ if (!showReqPermRationale()) {
+ videosViewModel.showAppSettingsRedirectDialog()
+ return@NoMediaPermissionScreen
+ }
+ launchPermRequest()
+ },
onExit = { onExit() },
aboutApp = { navController.navigate(Screens.ABOUT) },
)
diff --git a/app/src/main/java/com/infbyte/amuzeo/presentation/viewmodels/VideosViewModel.kt b/app/src/main/java/com/infbyte/amuzeo/presentation/viewmodels/VideosViewModel.kt
index 9a46065..5112609 100644
--- a/app/src/main/java/com/infbyte/amuzeo/presentation/viewmodels/VideosViewModel.kt
+++ b/app/src/main/java/com/infbyte/amuzeo/presentation/viewmodels/VideosViewModel.kt
@@ -297,4 +297,12 @@ class VideosViewModel(
state = state.copy(searchQuery = query)
}
}
+
+ fun showAppSettingsRedirectDialog() {
+ sideEffect = sideEffect.copy(showAppSettingsDialog = true)
+ }
+
+ fun hideAppSettingsRedirectDialog() {
+ sideEffect = sideEffect.copy(showAppSettingsDialog = false)
+ }
}
diff --git a/app/src/main/java/com/infbyte/amuzeo/repo/VideosRepo.kt b/app/src/main/java/com/infbyte/amuzeo/repo/VideosRepo.kt
index 7bd695d..768b4d0 100644
--- a/app/src/main/java/com/infbyte/amuzeo/repo/VideosRepo.kt
+++ b/app/src/main/java/com/infbyte/amuzeo/repo/VideosRepo.kt
@@ -99,17 +99,21 @@ class VideosRepo(private val context: Context) {
val fileId = videoPath.fileId()
- contentIds.add(fileId)
-
- _videos +=
- Video(
- item = item,
- folder = extractFolderName(path),
- fileId = fileId,
- thumbnail = context.createVideoThumbnail(Path(path), Size(640, 480)),
- )
+ val thumbnail = context.createVideoThumbnail(videoUri, Size(640, 480))
+
+ if (thumbnail != null) {
+ contentIds.add(fileId)
+
+ _videos +=
+ Video(
+ item = item,
+ folder = extractFolderName(path),
+ fileId = fileId,
+ thumbnail = thumbnail,
+ )
- _folderPaths.add(extractFolderPath(path))
+ _folderPaths.add(extractFolderPath(path))
+ }
}
query.close()
}
diff --git a/app/src/main/java/com/infbyte/amuzeo/utils/AmuzeoPermissions.kt b/app/src/main/java/com/infbyte/amuzeo/utils/AmuzeoPermissions.kt
index c87a275..0595b0d 100644
--- a/app/src/main/java/com/infbyte/amuzeo/utils/AmuzeoPermissions.kt
+++ b/app/src/main/java/com/infbyte/amuzeo/utils/AmuzeoPermissions.kt
@@ -1,6 +1,7 @@
package com.infbyte.amuzeo.utils
import android.Manifest
+import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
@@ -17,4 +18,13 @@ object AmuzeoPermissions {
.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED
}
+
+ fun Activity.showReqPermRationale(): Boolean =
+ shouldShowRequestPermissionRationale(
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Manifest.permission.READ_MEDIA_VIDEO
+ } else {
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ },
+ )
}
diff --git a/app/src/main/java/com/infbyte/amuzeo/utils/VideoUtils.kt b/app/src/main/java/com/infbyte/amuzeo/utils/VideoUtils.kt
index 2409bc6..57a06f8 100644
--- a/app/src/main/java/com/infbyte/amuzeo/utils/VideoUtils.kt
+++ b/app/src/main/java/com/infbyte/amuzeo/utils/VideoUtils.kt
@@ -5,13 +5,12 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.media.MediaMetadataRetriever.OPTION_PREVIOUS_SYNC
-import android.media.ThumbnailUtils
import android.net.Uri
import android.os.Build
+import android.util.Log
import android.util.Size
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
-import java.nio.file.Path
import kotlin.math.max
typealias VideoDuration = Long
@@ -25,68 +24,58 @@ fun Context.getVideoDuration(uri: Uri?): VideoDuration {
return duration
}
-fun Context.createVideoThumbnail(
- path: Path,
- size: Size,
-): ImageBitmap {
- val file = path.toFile()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- return ThumbnailUtils.createVideoThumbnail(
- file,
- size,
- null,
- ).asImageBitmap()
- }
- return createVideoThumbnail(Uri.fromFile(file), size)
-}
-
fun Context.createVideoThumbnail(
uri: Uri,
size: Size,
-): ImageBitmap {
- val metaRetriever = MediaMetadataRetriever()
- metaRetriever.setDataSource(this, uri)
- val thumbnailsBytes = metaRetriever.embeddedPicture
+): ImageBitmap? {
+ return try {
+ val metaRetriever = MediaMetadataRetriever()
+ metaRetriever.setDataSource(this, uri)
+ val thumbnailsBytes = metaRetriever.embeddedPicture
- if (thumbnailsBytes != null) {
- return BitmapFactory.decodeByteArray(thumbnailsBytes, 0, thumbnailsBytes.size).asImageBitmap()
- }
+ if (thumbnailsBytes != null) {
+ return BitmapFactory.decodeByteArray(thumbnailsBytes, 0, thumbnailsBytes.size).asImageBitmap()
+ }
- val width =
- metaRetriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH,
- )?.toFloat() ?: size.width.toFloat()
- val height =
- metaRetriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT,
- )?.toFloat() ?: size.height.toFloat()
+ val width =
+ metaRetriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH,
+ )?.toFloat() ?: size.width.toFloat()
+ val height =
+ metaRetriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT,
+ )?.toFloat() ?: size.height.toFloat()
- val widthRatio = size.width.toFloat() / width
- val heightRatio = size.height.toFloat() / height
+ val widthRatio = size.width.toFloat() / width
+ val heightRatio = size.height.toFloat() / height
- val ratio = max(widthRatio, heightRatio)
+ val ratio = max(widthRatio, heightRatio)
- if (ratio > 1) {
- val reqWidth = width * ratio
- val reqHeight = height * ratio
+ if (ratio > 1) {
+ val reqWidth = width * ratio
+ val reqHeight = height * ratio
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- val frame =
- metaRetriever.getScaledFrameAtTime(
- -1,
- OPTION_PREVIOUS_SYNC,
- reqWidth.toInt(),
- reqHeight.toInt(),
- )
- metaRetriever.release()
- return frame?.asImageBitmap()!!
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ val frame =
+ metaRetriever.getScaledFrameAtTime(
+ -1,
+ OPTION_PREVIOUS_SYNC,
+ reqWidth.toInt(),
+ reqHeight.toInt(),
+ )
+ metaRetriever.release()
+ return frame?.asImageBitmap()!!
+ }
}
- }
- // Should be scaled according to requested size
- val frame = metaRetriever.frameAtTime
- metaRetriever.release()
- return frame?.asImageBitmap()!!
+ // Should be scaled according to requested size
+ val frame = metaRetriever.frameAtTime
+ metaRetriever.release()
+ frame?.asImageBitmap()
+ } catch (e: Exception) {
+ Log.e("Video Thumbnail", "Failed to create thumbnail with exception: $e")
+ null
+ }
}
fun VideoDuration.format(): String {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 08d8065..0833044 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,4 +17,5 @@
Apply
Cancel
Preparing your videos...
+ To play video, Amuzeo requires Video permisson.