Skip to content

Commit 428347f

Browse files
committed
First version
1 parent 98eb3cc commit 428347f

39 files changed

Lines changed: 743 additions & 92 deletions

.idea/compiler.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/deploymentTargetSelector.xml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/gradle.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
plugins {
22
alias(libs.plugins.android.application)
3-
alias(libs.plugins.kotlin.compose)
3+
alias(libs.plugins.jetbrains.kotlin.android)
44
}
55

66
android {
77
namespace = "com.example.hermesplay"
8-
compileSdk {
9-
version = release(36) {
10-
minorApiLevel = 1
11-
}
12-
}
8+
compileSdk = 34
139

1410
defaultConfig {
1511
applicationId = "com.example.hermesplay"
1612
minSdk = 26
17-
targetSdk = 36
13+
targetSdk = 34
1814
versionCode = 1
1915
versionName = "1.0"
2016

2117
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18+
vectorDrawables {
19+
useSupportLibrary = true
20+
}
2221
}
2322

2423
buildTypes {
@@ -30,29 +29,58 @@ android {
3029
)
3130
}
3231
}
32+
33+
// Upgraded to Java 17
3334
compileOptions {
34-
sourceCompatibility = JavaVersion.VERSION_11
35-
targetCompatibility = JavaVersion.VERSION_11
35+
sourceCompatibility = JavaVersion.VERSION_17
36+
targetCompatibility = JavaVersion.VERSION_17
37+
}
38+
39+
// Explicitly added to align Kotlin with Java 17
40+
kotlinOptions {
41+
jvmTarget = "17"
3642
}
43+
3744
buildFeatures {
3845
compose = true
3946
}
47+
48+
// Removed the duplicate block that was here
49+
composeOptions {
50+
kotlinCompilerExtensionVersion = "1.5.10"
51+
}
52+
53+
packaging {
54+
resources {
55+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
56+
}
57+
}
4058
}
4159

4260
dependencies {
43-
implementation(platform(libs.androidx.compose.bom))
44-
implementation(libs.androidx.activity.compose)
45-
implementation(libs.androidx.compose.material3)
46-
implementation(libs.androidx.compose.ui)
47-
implementation(libs.androidx.compose.ui.graphics)
48-
implementation(libs.androidx.compose.ui.tooling.preview)
4961
implementation(libs.androidx.core.ktx)
5062
implementation(libs.androidx.lifecycle.runtime.ktx)
63+
implementation(libs.androidx.activity.compose)
64+
implementation(platform(libs.androidx.compose.bom))
65+
implementation(libs.androidx.ui)
66+
implementation(libs.androidx.ui.graphics)
67+
implementation(libs.androidx.ui.tooling.preview)
68+
implementation(libs.androidx.material3)
69+
70+
// HermesPlay Dependencies
71+
implementation(libs.androidx.media3.exoplayer)
72+
implementation(libs.androidx.media3.ui)
73+
implementation(libs.coil.compose)
74+
implementation(libs.coil.video)
75+
implementation(libs.androidx.navigation.compose)
76+
implementation(libs.androidx.documentfile)
77+
implementation("androidx.compose.material:material-icons-extended")
78+
5179
testImplementation(libs.junit)
52-
androidTestImplementation(platform(libs.androidx.compose.bom))
53-
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
54-
androidTestImplementation(libs.androidx.espresso.core)
5580
androidTestImplementation(libs.androidx.junit)
56-
debugImplementation(libs.androidx.compose.ui.test.manifest)
57-
debugImplementation(libs.androidx.compose.ui.tooling)
81+
androidTestImplementation(libs.androidx.espresso.core)
82+
androidTestImplementation(platform(libs.androidx.compose.bom))
83+
androidTestImplementation(libs.androidx.ui.test.junit4)
84+
debugImplementation(libs.androidx.ui.tooling)
85+
debugImplementation(libs.androidx.ui.test.manifest)
5886
}

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
3+
xmlns:tools="http://schemas.android.com/tools" >
44

55
<application
66
android:allowBackup="true"
@@ -10,12 +10,13 @@
1010
android:label="@string/app_name"
1111
android:roundIcon="@mipmap/ic_launcher_round"
1212
android:supportsRtl="true"
13-
android:theme="@style/Theme.HermesPlay">
13+
android:theme="@style/Theme.HermesPlay" >
1414
<activity
1515
android:name=".MainActivity"
1616
android:exported="true"
1717
android:label="@string/app_name"
18-
android:theme="@style/Theme.HermesPlay">
18+
android:screenOrientation="sensorLandscape"
19+
android:theme="@style/Theme.HermesPlay" >
1920
<intent-filter>
2021
<action android:name="android.intent.action.MAIN" />
2122

399 KB
Loading
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package com.example.hermesplay
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.lazy.grid.GridCells
7+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
8+
import androidx.compose.foundation.lazy.grid.items
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material.icons.Icons
11+
import androidx.compose.material.icons.filled.ArrowBack
12+
import androidx.compose.material.icons.filled.Folder
13+
import androidx.compose.material.icons.filled.Home
14+
import androidx.compose.material.icons.filled.PlayArrow
15+
import androidx.compose.material.icons.filled.Refresh
16+
import androidx.compose.material.icons.filled.Sync
17+
import androidx.compose.material3.*
18+
import androidx.compose.runtime.*
19+
import androidx.compose.ui.Alignment
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.draw.clip
22+
import androidx.compose.ui.graphics.Color
23+
import androidx.compose.ui.layout.ContentScale
24+
import androidx.compose.ui.platform.LocalContext
25+
import androidx.compose.ui.text.style.TextAlign
26+
import androidx.compose.ui.text.style.TextOverflow
27+
import androidx.compose.ui.unit.dp
28+
import coil.compose.AsyncImage
29+
import coil.request.ImageRequest
30+
import coil.request.videoFrameMillis
31+
import com.example.hermesplay.models.MediaItem
32+
import com.example.hermesplay.viewmodels.UiState
33+
34+
@OptIn(ExperimentalMaterial3Api::class)
35+
@Composable
36+
fun DirectoryScreen(
37+
uiState: UiState,
38+
onFolderClick: (MediaItem.Folder) -> Unit,
39+
onVideoClick: (MediaItem.Video) -> Unit,
40+
onNavigateUp: () -> Unit,
41+
onJumpToRoot: () -> Unit,
42+
onRefresh: () -> Unit,
43+
onRescanAll: () -> Unit // NEW Callback
44+
) {
45+
var gridSize by remember { mutableFloatStateOf(180f) }
46+
47+
Scaffold(
48+
containerColor = Color.Black,
49+
topBar = {
50+
TopAppBar(
51+
title = { Text("HermesPlay") },
52+
colors = TopAppBarDefaults.topAppBarColors(
53+
containerColor = Color.Black,
54+
titleContentColor = Color.White,
55+
actionIconContentColor = Color.White
56+
),
57+
actions = {
58+
Row(
59+
verticalAlignment = Alignment.CenterVertically,
60+
modifier = Modifier.padding(end = 16.dp).width(180.dp)
61+
) {
62+
Text("Size", color = Color.Gray, style = MaterialTheme.typography.labelMedium)
63+
Spacer(modifier = Modifier.width(8.dp))
64+
Slider(
65+
value = gridSize,
66+
onValueChange = { gridSize = it },
67+
valueRange = 120f..400f,
68+
colors = SliderDefaults.colors(thumbColor = Color.White, activeTrackColor = Color.DarkGray)
69+
)
70+
}
71+
72+
if (uiState is UiState.Success) {
73+
if (uiState.isRoot) {
74+
// Show Global Rescan if at the root
75+
IconButton(onClick = onRescanAll) {
76+
Icon(Icons.Default.Sync, contentDescription = "Rescan All Folders")
77+
}
78+
} else {
79+
// Show Folder Refresh and Home if in a sub-folder
80+
IconButton(onClick = onRefresh) {
81+
Icon(Icons.Default.Refresh, contentDescription = "Refresh Folder")
82+
}
83+
IconButton(onClick = onJumpToRoot) {
84+
Icon(Icons.Default.Home, contentDescription = "Go to Root")
85+
}
86+
}
87+
}
88+
}
89+
)
90+
}
91+
) { paddingValues ->
92+
Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
93+
when (uiState) {
94+
is UiState.Loading -> CircularProgressIndicator(modifier = Modifier.align(Alignment.Center), color = Color.White)
95+
is UiState.Error -> Text(text = uiState.message, color = MaterialTheme.colorScheme.error, modifier = Modifier.align(Alignment.Center))
96+
is UiState.Success -> {
97+
if (uiState.items.isEmpty() && uiState.isRoot) {
98+
Text("This folder is empty!", color = Color.White, modifier = Modifier.align(Alignment.Center))
99+
} else {
100+
LazyVerticalGrid(
101+
columns = GridCells.Adaptive(minSize = gridSize.dp),
102+
contentPadding = PaddingValues(16.dp),
103+
horizontalArrangement = Arrangement.spacedBy(16.dp),
104+
verticalArrangement = Arrangement.spacedBy(16.dp)
105+
) {
106+
if (!uiState.isRoot) { item { BackNavigationCard(onClick = onNavigateUp) } }
107+
108+
items(uiState.items, key = { it.uri.toString() }) { item ->
109+
MediaItemCard(
110+
item = item,
111+
onClick = {
112+
when (item) {
113+
is MediaItem.Folder -> onFolderClick(item)
114+
is MediaItem.Video -> onVideoClick(item)
115+
}
116+
}
117+
)
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
}
125+
}
126+
127+
@Composable
128+
fun BackNavigationCard(onClick: () -> Unit) {
129+
Card(
130+
modifier = Modifier.fillMaxWidth().aspectRatio(1f).clickable(onClick = onClick).clip(RoundedCornerShape(12.dp)),
131+
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
132+
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
133+
) {
134+
Column(
135+
modifier = Modifier.fillMaxSize(),
136+
verticalArrangement = Arrangement.Center,
137+
horizontalAlignment = Alignment.CenterHorizontally
138+
) {
139+
Icon(Icons.Default.ArrowBack, contentDescription = "Go Back", modifier = Modifier.size(64.dp), tint = Color.White)
140+
Spacer(modifier = Modifier.height(8.dp))
141+
Text("Back", style = MaterialTheme.typography.titleLarge, color = Color.White)
142+
}
143+
}
144+
}
145+
146+
@Composable
147+
fun MediaItemCard(item: MediaItem, onClick: () -> Unit) {
148+
Card(
149+
modifier = Modifier.fillMaxWidth().aspectRatio(1f).clickable(onClick = onClick).clip(RoundedCornerShape(12.dp)),
150+
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
151+
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
152+
) {
153+
Box(modifier = Modifier.fillMaxSize()) {
154+
val context = LocalContext.current
155+
156+
// SMART LOADING: Only time-shift if we are forced to extract a video frame
157+
val imageRequest = remember(item.uri, item.thumbnailUri) {
158+
val isExtractingFromVideo = item.thumbnailUri == null || item.thumbnailUri == item.uri
159+
160+
ImageRequest.Builder(context)
161+
.data(item.thumbnailUri ?: item.uri)
162+
.apply {
163+
if (isExtractingFromVideo) {
164+
videoFrameMillis(60_000)
165+
}
166+
}
167+
.crossfade(true)
168+
.build()
169+
}
170+
171+
AsyncImage(
172+
model = imageRequest,
173+
contentDescription = item.name,
174+
contentScale = ContentScale.Crop,
175+
modifier = Modifier.fillMaxSize()
176+
)
177+
178+
Box(modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth().background(Color.Black.copy(alpha = 0.7f)).padding(8.dp)) {
179+
Text(text = item.name.substringBeforeLast("."), color = Color.White, style = MaterialTheme.typography.labelLarge, maxLines = 2, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
180+
}
181+
182+
Box(modifier = Modifier.align(Alignment.TopEnd).padding(8.dp).background(Color.Black.copy(alpha = 0.6f), RoundedCornerShape(8.dp)).padding(4.dp)) {
183+
when (item) {
184+
is MediaItem.Folder -> Icon(Icons.Default.Folder, contentDescription = "Folder", tint = Color.Yellow)
185+
is MediaItem.Video -> Icon(Icons.Default.PlayArrow, contentDescription = "Play", tint = Color.White)
186+
}
187+
}
188+
}
189+
}
190+
}

0 commit comments

Comments
 (0)