Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
*.iml
*.jks
*.aab
*.aar
*.base64
*.json
*/libs
.gradle
/local.properties
.idea
Expand Down
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.infbyte.amuzeo.models

data class AmuzeoSideEffect(
val showSplash: Boolean = true,
val showAppSettingsDialog: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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 -> {
Expand Down Expand Up @@ -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) },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
24 changes: 14 additions & 10 deletions app/src/main/java/com/infbyte/amuzeo/repo/VideosRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/infbyte/amuzeo/utils/AmuzeoPermissions.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
},
)
}
95 changes: 42 additions & 53 deletions app/src/main/java/com/infbyte/amuzeo/utils/VideoUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
<string name="amuzeo_apply">Apply</string>
<string name="amuzeo_cancel">Cancel</string>
<string name="amuzeo_preparing">Preparing your videos...</string>
<string name="amuzeo_perm_rationale">To play video, Amuzeo requires Video permisson.</string>
</resources>