This commit is contained in:
Binondi
2024-12-16 19:47:57 +05:30
parent 0fe7c4c25f
commit 24d50a3c01
5 changed files with 314 additions and 12 deletions

View File

@@ -4,7 +4,8 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2
import devs.org.calculator.adapters.ImagePreviewAdapter import devs.org.calculator.adapters.ImagePreviewAdapter
import devs.org.calculator.callbacks.DialogActionsCallback import devs.org.calculator.callbacks.DialogActionsCallback
import devs.org.calculator.databinding.ActivityPreviewBinding import devs.org.calculator.databinding.ActivityPreviewBinding
@@ -12,6 +13,7 @@ import devs.org.calculator.utils.DialogUtil
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import devs.org.calculator.R
class PreviewActivity : AppCompatActivity() { class PreviewActivity : AppCompatActivity() {
@@ -39,6 +41,14 @@ class PreviewActivity : AppCompatActivity() {
setupImagePreview() setupImagePreview()
clickListeners() clickListeners()
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
}
})
} }
private fun setupFileType() { private fun setupFileType() {
@@ -73,6 +83,25 @@ class PreviewActivity : AppCompatActivity() {
val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString() val fileName = FileManager.FileName(this).getFileNameFromUri(fileUri).toString()
} }
override fun onPause() {
super.onPause()
if (filetype == FileManager.FileType.AUDIO) {
(binding.viewPager.adapter as? ImagePreviewAdapter)?.currentMediaPlayer?.pause()
}
}
override fun onDestroy() {
super.onDestroy()
if (filetype == FileManager.FileType.AUDIO) {
(binding.viewPager.adapter as? ImagePreviewAdapter)?.let { adapter ->
adapter.currentMediaPlayer?.release()
adapter.currentMediaPlayer = null
}
}
}
private fun clickListeners() { private fun clickListeners() {
binding.delete.setOnClickListener { binding.delete.setOnClickListener {
val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype) val fileUri = FileManager.FileManager().getContentUriImage(this, files[binding.viewPager.currentItem], filetype)
@@ -147,4 +176,6 @@ class PreviewActivity : AppCompatActivity() {
onBackPressed() onBackPressed()
return true return true
} }
} }

View File

@@ -82,11 +82,42 @@ class FileAdapter(
} }
itemView.setOnClickListener { itemView.setOnClickListener {
val intent = Intent(context, PreviewActivity::class.java).apply {
putExtra("type", fileTypes) when(fileType){
putExtra("position", position) FileManager.FileType.AUDIO -> {
// Create an intent to play audio using available audio players
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(FileManager.FileManager().getContentUriImage(context, file, fileType), "audio/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
try {
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, "No audio player found!", Toast.LENGTH_SHORT).show()
}
}
FileManager.FileType.DOCUMENT -> {
// Create an intent to open the document using available viewers or file managers
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(FileManager.FileManager().getContentUriImage(context, file, fileType), "*/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
try {
context.startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, "No suitable app found to open this document!", Toast.LENGTH_SHORT).show()
}
}
else -> {
val intent = Intent(context, PreviewActivity::class.java).apply {
putExtra("type", fileTypes)
putExtra("position", position)
}
context.startActivity(intent)
}
} }
context.startActivity(intent)
} }
itemView.setOnLongClickListener { itemView.setOnLongClickListener {

View File

@@ -1,11 +1,15 @@
package devs.org.calculator.adapters package devs.org.calculator.adapters
import android.content.Context import android.content.Context
import android.media.MediaPlayer
import android.net.Uri import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.MediaController import android.widget.MediaController
import android.widget.SeekBar
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@@ -13,16 +17,20 @@ import devs.org.calculator.adapters.FileAdapter.FileDiffCallback
import devs.org.calculator.databinding.ViewpagerItemsBinding import devs.org.calculator.databinding.ViewpagerItemsBinding
import devs.org.calculator.utils.FileManager import devs.org.calculator.utils.FileManager
import java.io.File import java.io.File
import devs.org.calculator.R
class ImagePreviewAdapter( class ImagePreviewAdapter(
private val context: Context, private val context: Context,
private var fileType: FileManager.FileType private var fileType: FileManager.FileType
) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() { ) : RecyclerView.Adapter<ImagePreviewAdapter.ImageViewHolder>() {
// Use AsyncListDiffer for managing the list
private val differ = AsyncListDiffer(this, FileDiffCallback()) private val differ = AsyncListDiffer(this, FileDiffCallback())
var currentMediaPlayer: MediaPlayer? = null
var isMediaPlayerPrepared = false
var currentViewHolder: ImageViewHolder? = null
private var currentPlayingPosition = -1
private var isPlaying = false
// Expose data management through differ
var images: List<File> var images: List<File>
get() = differ.currentList get() = differ.currentList
set(value) = differ.submitList(value) set(value) = differ.submitList(value)
@@ -35,15 +43,35 @@ class ImagePreviewAdapter(
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val imageUrl = images[position] val imageUrl = images[position]
holder.bind(imageUrl) holder.bind(imageUrl)
currentViewHolder = holder
currentMediaPlayer?.let {
if (it.isPlaying) it.pause()
it.seekTo(0)
}
currentMediaPlayer = null
isMediaPlayerPrepared = false
if (currentMediaPlayer?.isPlaying == true) {
currentMediaPlayer?.stop()
currentMediaPlayer?.release()
}
currentMediaPlayer = null
} }
override fun getItemCount(): Int = images.size override fun getItemCount(): Int = images.size
inner class ImageViewHolder(val binding: ViewpagerItemsBinding) : RecyclerView.ViewHolder(binding.root) { inner class ImageViewHolder(private val binding: ViewpagerItemsBinding) : RecyclerView.ViewHolder(binding.root) {
private var mediaPlayer: MediaPlayer? = null
private var seekHandler = Handler(Looper.getMainLooper())
private var seekRunnable: Runnable? = null
fun bind(file: File) { fun bind(file: File) {
when (fileType) { when (fileType) {
FileManager.FileType.VIDEO -> { FileManager.FileType.VIDEO -> {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
binding.audioBg.visibility = View.GONE
binding.videoView.visibility = View.VISIBLE binding.videoView.visibility = View.VISIBLE
val videoUri = Uri.fromFile(file) val videoUri = Uri.fromFile(file)
@@ -73,19 +101,131 @@ class ImagePreviewAdapter(
FileManager.FileType.IMAGE -> { FileManager.FileType.IMAGE -> {
binding.imageView.visibility = View.VISIBLE binding.imageView.visibility = View.VISIBLE
binding.videoView.visibility = View.GONE binding.videoView.visibility = View.GONE
binding.audioBg.visibility = View.GONE
Glide.with(context) Glide.with(context)
.load(file) .load(file)
.into(binding.imageView) .into(binding.imageView)
} }
FileManager.FileType.AUDIO -> { FileManager.FileType.AUDIO -> {
// Handle audio if necessary binding.imageView.visibility = View.GONE
binding.audioBg.visibility = View.VISIBLE
binding.videoView.visibility = View.GONE
binding.audioTitle.text = file.name
setupAudioPlayer(file)
setupSeekBar()
setupPlaybackControls()
} }
else -> { else -> {
// Handle other types if necessary binding.imageView.visibility = View.VISIBLE
binding.audioBg.visibility = View.GONE
binding.videoView.visibility = View.GONE
} }
} }
} }
private fun setupAudioPlayer(file: File) {
mediaPlayer = MediaPlayer().apply {
setDataSource(file.absolutePath)
setOnPreparedListener { mp ->
binding.audioSeekBar.max = mp.duration
isMediaPlayerPrepared = true
}
setOnCompletionListener {
// isPlaying = false
binding.playPause.setImageResource(R.drawable.play)
binding.audioSeekBar.progress = 0
seekHandler.removeCallbacks(seekRunnable!!)
}
prepareAsync()
}
}
private fun setupSeekBar() {
binding.audioSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
mediaPlayer?.seekTo(progress)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
seekRunnable = Runnable {
mediaPlayer?.let { mp ->
if (mp.isPlaying) {
binding.audioSeekBar.progress = mp.currentPosition
seekHandler.postDelayed(seekRunnable!!, 100)
}
}
}
}
private fun setupPlaybackControls() {
binding.playPause.setOnClickListener {
if (isPlaying) {
pauseAudio()
} else {
playAudio()
}
}
binding.preview.setOnClickListener {
mediaPlayer?.let { mp ->
val newPosition = mp.currentPosition - 10000
mp.seekTo(maxOf(0, newPosition))
binding.audioSeekBar.progress = mp.currentPosition
}
}
binding.next.setOnClickListener {
mediaPlayer?.let { mp ->
val newPosition = mp.currentPosition + 10000
mp.seekTo(minOf(mp.duration, newPosition))
binding.audioSeekBar.progress = mp.currentPosition
}
}
}
private fun playAudio() {
mediaPlayer?.let { mp ->
if (currentPlayingPosition != -1 && currentPlayingPosition != adapterPosition) {
currentViewHolder?.pauseAudio()
}
mp.start()
isPlaying = true
binding.playPause.setImageResource(R.drawable.pause)
seekHandler.post(seekRunnable!!)
currentPlayingPosition = adapterPosition
currentViewHolder = this
}
}
private fun pauseAudio() {
mediaPlayer?.let { mp ->
if (mp.isPlaying) {
mp.pause()
isPlaying = false
binding.playPause.setImageResource(R.drawable.play)
seekHandler.removeCallbacks(seekRunnable!!)
}
}
}
fun releaseMediaPlayer() {
mediaPlayer?.let { mp ->
if (mp.isPlaying) {
mp.stop()
}
mp.release()
mediaPlayer = null
isPlaying = false
seekHandler.removeCallbacks(seekRunnable!!)
}
}
private fun playVideoAtPosition(position: Int) { private fun playVideoAtPosition(position: Int) {
val nextFile = images[position] val nextFile = images[position]
if (fileType == FileManager.FileType.VIDEO) { if (fileType == FileManager.FileType.VIDEO) {
@@ -95,5 +235,10 @@ class ImagePreviewAdapter(
} }
} }
} }
override fun onViewRecycled(holder: ImageViewHolder) {
super.onViewRecycled(holder)
holder.releaseMediaPlayer()
}
} }

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="4dp" android:layout_margin="4dp"
app:cardCornerRadius="8dp"> app:cardCornerRadius="8dp">
@@ -15,6 +17,7 @@
app:zoomage_animateOnReset="true" app:zoomage_animateOnReset="true"
app:zoomage_autoResetMode="UNDER" app:zoomage_autoResetMode="UNDER"
app:zoomage_autoCenter="true" app:zoomage_autoCenter="true"
android:visibility="gone"
app:zoomage_zoomable="true" app:zoomage_zoomable="true"
app:zoomage_translatable="true" app:zoomage_translatable="true"
app:zoomage_minScale="0.6" app:zoomage_minScale="0.6"
@@ -24,6 +27,99 @@
<VideoView <VideoView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone"
android:id="@+id/videoView"/> android:id="@+id/videoView"/>
<com.google.android.material.card.MaterialCardView
android:id="@+id/audioBg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
style="@style/Widget.Material3.CardView.Outlined">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="5dp"
android:src="@drawable/music"
android:padding="3dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="audio.mp3"
android:layout_margin="5dp"
android:id="@+id/audioTitle"/>
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:id="@+id/audioSeekBar"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<com.google.android.material.card.MaterialCardView
android:layout_width="40dp"
android:layout_height="40dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/previous"
android:padding="3dp"
android:id="@+id/preview"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="40dp"
android:layout_margin="8dp"
android:layout_height="40dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/play"
android:padding="3dp"
android:id="@+id/playPause"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="40dp"
android:layout_height="40dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="3dp"
android:src="@drawable/next"
android:id="@+id/next"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@@ -5,7 +5,6 @@
<item name="colorPrimary">@color/primary</item> <item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariant</item> <item name="colorPrimaryVariant">@color/colorPrimaryVariant</item>
<item name="colorOnPrimary">@color/black</item> <item name="colorOnPrimary">@color/black</item>
<item name="android:background">@color/black</item>
<!-- Secondary brand color --> <!-- Secondary brand color -->
<item name="colorSecondary">@color/colorSecondary</item> <item name="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/colorSecondary</item> <item name="colorSecondaryVariant">@color/colorSecondary</item>