Skip to content
Draft
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
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ android {
}

dependencies {
implementation(libs.androidx.compose.material3)
"baselineProfile"(project(":baselineprofile"))
implementation(libs.androidx.profileinstaller)
coreLibraryDesugaring(libs.desugar.jdk.libs)
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@
android:theme="@style/Theme.FileExplorer"
android:windowSoftInputMode="adjustResize" />

<activity
android:name=".screen.playlist.PlaylistActivity"
android:exported="false"
android:label="@string/manage_playlists"
android:theme="@style/Theme.FileExplorer"
android:windowSoftInputMode="adjustResize" />

<activity
android:name=".screen.main.MainActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,14 @@ class LocalFileHolder(val file: File) : ContentHolder() {
contentCount.folders
)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LocalFileHolder) return false
return file.absolutePath == other.file.absolutePath
}

override fun hashCode(): Int {
return file.absolutePath.hashCode()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.automirrored.rounded.PlaylistAdd
import androidx.compose.material.icons.rounded.BookmarkAdd
import androidx.compose.material.icons.rounded.Compress
import androidx.compose.material.icons.rounded.ContentCut
Expand All @@ -18,6 +19,7 @@ import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Merge
import androidx.compose.material.icons.rounded.OpenInNewOff
import androidx.compose.material.icons.rounded.PlaylistAdd
import androidx.compose.material.icons.rounded.PushPin
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.HorizontalDivider
Expand Down Expand Up @@ -53,12 +55,14 @@ import com.raival.compose.file.explorer.screen.main.tab.files.holder.VirtualFile
import com.raival.compose.file.explorer.screen.main.tab.files.holder.ZipFileHolder
import com.raival.compose.file.explorer.screen.main.tab.files.misc.DefaultOpeningMethods
import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.apkBundleFileType
import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.audioFileType
import com.raival.compose.file.explorer.screen.main.tab.files.task.ApksMergeTask
import com.raival.compose.file.explorer.screen.main.tab.files.task.ApksMergeTaskParameters
import com.raival.compose.file.explorer.screen.main.tab.files.task.CompressTask
import com.raival.compose.file.explorer.screen.main.tab.files.task.CopyTask
import com.raival.compose.file.explorer.screen.main.tab.files.ui.FileIcon
import com.raival.compose.file.explorer.screen.main.tab.files.ui.ItemRow
import com.raival.compose.file.explorer.screen.viewer.audio.ui.PlaylistBottomSheet

@Composable
fun FileOptionsMenuDialog(
Expand All @@ -79,6 +83,17 @@ fun FileOptionsMenuDialog(
val isMultipleSelection = selectedFilesCount > 1
val isSingleFile = !isMultipleSelection && targetContentHolder.isFile()
val isSingleFolder = !isMultipleSelection && targetContentHolder.isFolder
val isAudioFile = isSingleFile && targetContentHolder is LocalFileHolder &&
audioFileType.contains(targetContentHolder.file.extension)

// Check for multiple audio files
val audioFiles = targetFiles.filter { file ->
file is LocalFileHolder && file.isFile() && audioFileType.contains(file.file.extension)
}.map { it as LocalFileHolder }
val hasMultipleAudioFiles = audioFiles.size > 1
val hasAnyAudioFiles = audioFiles.isNotEmpty()

var showPlaylistDialog by remember { mutableStateOf(false) }

var hasFolders = false
tab.selectedFiles.forEach {
Expand Down Expand Up @@ -267,6 +282,12 @@ fun FileOptionsMenuDialog(
tab.unselectAllFiles()
}

if (isAudioFile) {
FileOption(Icons.AutoMirrored.Rounded.PlaylistAdd, stringResource(R.string.add_to_playlist)) {
showPlaylistDialog = true
}
}

if (apkBundleFileType.contains(targetContentHolder.file.extension)) {
FileOption(Icons.Rounded.Merge, stringResource(R.string.convert_to_apk)) {
onDismissRequest()
Expand All @@ -281,6 +302,12 @@ fun FileOptionsMenuDialog(
}
}

if (hasMultipleAudioFiles) {
FileOption(Icons.AutoMirrored.Rounded.PlaylistAdd, stringResource(R.string.add_multiple_to_playlist)) {
showPlaylistDialog = true
}
}

if (tab.activeFolder !is ZipFileHolder) {
FileOption(Icons.Rounded.Compress, stringResource(R.string.compress)) {
CompressTask(targetFiles).let { task ->
Expand All @@ -307,6 +334,24 @@ fun FileOptionsMenuDialog(
}
}
}

// Playlist dialog for audio files
if (isAudioFile || hasAnyAudioFiles) {
PlaylistBottomSheet(
isVisible = showPlaylistDialog,
onDismiss = {
showPlaylistDialog = false
onDismissRequest()
},
onPlaylistSelected = { playlist ->
showPlaylistDialog = false
onDismissRequest()
tab.unselectAllFiles()
},
selectedSong = if (isAudioFile && targetContentHolder is LocalFileHolder) targetContentHolder else null,
selectedSongs = if (hasMultipleAudioFiles) audioFiles else emptyList()
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.raival.compose.file.explorer.screen.main.tab.home

import android.content.ContentResolver
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
import androidx.compose.material.icons.rounded.Android
import androidx.compose.material.icons.rounded.Archive
import androidx.compose.material.icons.rounded.AudioFile
import androidx.compose.material.icons.rounded.Image
import androidx.compose.material.icons.rounded.QueueMusic
import androidx.compose.material.icons.rounded.VideoFile
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
Expand Down Expand Up @@ -108,6 +114,24 @@ class HomeTab : Tab() {
)
)

add(
HomeCategory(
name = globalClass.getString(R.string.playlists),
icon = Icons.Rounded.QueueMusic,
onClick = {
try {
val context = globalClass.applicationContext
val intent = Intent(context, com.raival.compose.file.explorer.screen.playlist.PlaylistActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
context.startActivity(intent)
} catch (e: Exception) {
// Log the error or handle it gracefully
e.printStackTrace()
}
}
)
)

add(
HomeCategory(
name = globalClass.getString(R.string.documents),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.raival.compose.file.explorer.screen.playlist

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.raival.compose.file.explorer.screen.playlist.ui.PlaylistManagerScreen
import com.raival.compose.file.explorer.screen.viewer.audio.AudioPlayerActivity
import com.raival.compose.file.explorer.screen.viewer.audio.PlaylistManager
import com.raival.compose.file.explorer.theme.FileExplorerTheme

class PlaylistActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
FileExplorerTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
PlaylistManagerScreen(
onBackPressed = { onBackPressedDispatcher.onBackPressed() },
onPlayPlaylist = { playlist, startIndex ->
PlaylistManager.getInstance().loadPlaylist(playlist.id)
if (playlist.songs.isNotEmpty() && startIndex < playlist.songs.size) {
val firstSong = playlist.songs[startIndex]
val intent = Intent(this@PlaylistActivity, AudioPlayerActivity::class.java).apply {
data = Uri.fromFile(firstSong.file)
putExtra("startIndex", startIndex)
putExtra("fromPlaylist", true)
putExtra("uid", firstSong.uid)
}
startActivity(intent)
finish()
}
}
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.raival.compose.file.explorer.screen.playlist.ui

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MusicNote
import androidx.compose.material.icons.filled.MusicOff
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.raival.compose.file.explorer.R
import kotlinx.coroutines.delay

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun EmptyPlaylistContent() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(24.dp)
) {
var showAnimation by remember { mutableStateOf(true) }

LaunchedEffect(Unit) {
while (true) {
delay(3000)
showAnimation = !showAnimation
delay(200)
showAnimation = !showAnimation
}
}

Box(
modifier = Modifier
.size(100.dp)
.clip(CircleShape)
.background(
Brush.radialGradient(
colors = listOf(
MaterialTheme.colorScheme.secondaryContainer,
MaterialTheme.colorScheme.secondary.copy(alpha = 0.3f)
)
)
)
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.5f),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
AnimatedContent(
targetState = showAnimation,
label = "EmptyAnimation",
transitionSpec = {
(fadeIn(tween(500)) + scaleIn(tween(500))) togetherWith
(fadeOut(tween(200)) + scaleOut(tween(200)))
}
) { state ->
Icon(
imageVector = if (state) Icons.Default.MusicNote else Icons.Default.MusicOff,
contentDescription = null,
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}

Text(
text = stringResource(R.string.empty_playlist),
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)

Text(
text = stringResource(R.string.empty_playlist_description),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth(0.8f)
.alpha(0.8f)
)
}
}
}
Loading
Loading