2 Commits
v1.3 ... v1.5

Author SHA1 Message Date
d9b886cdc6 Fix unclickable tabs: pad for status/nav bars under enforced edge-to-edge
All checks were successful
Build & Release APK / build (push) Successful in 6m49s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 01:29:33 -07:00
cc800e756b Add Reading tab with Jhanas Guide and Retreat Instructions PDFs
Some checks failed
Build & Release APK / build (push) Has been cancelled
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 01:23:16 -07:00
6 changed files with 88 additions and 0 deletions

View File

@@ -51,6 +51,7 @@ android {
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2024.10.01")
implementation(composeBom)
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.9.3")

View File

@@ -27,6 +27,16 @@
android:name=".TimerService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.marcus.meditationtimer.files"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

Binary file not shown.

Binary file not shown.

View File

@@ -1,10 +1,14 @@
package com.marcus.meditationtimer
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.core.content.FileProvider
import java.io.File
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
@@ -22,8 +26,10 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -128,6 +134,10 @@ private fun App() {
modifier = Modifier
.fillMaxSize()
.background(DeepBlue)
// app targets SDK 35 → edge-to-edge is forced; without these the tab
// row sits under the status bar and taps never reach it
.statusBarsPadding()
.navigationBarsPadding()
) {
TabRow(
selectedTabIndex = tab,
@@ -136,6 +146,7 @@ private fun App() {
) {
Tab(selected = tab == 0, onClick = { tab = 0 }, text = { Text("Timer") })
Tab(selected = tab == 1, onClick = { tab = 1 }, text = { Text("History") })
Tab(selected = tab == 2, onClick = { tab = 2 }, text = { Text("Reading") })
}
when (tab) {
0 -> TimerTab()
@@ -143,6 +154,7 @@ private fun App() {
onEditSession = { ratingFor = it },
onOpenToken = { showTokenDialog = true },
)
2 -> ReadingTab()
}
}
@@ -430,6 +442,67 @@ private fun SessionRow(s: Session, onClick: () -> Unit) {
}
}
// -------------------------------------------------------------- Reading tab
private data class Doc(val title: String, val subtitle: String, val asset: String)
private val DOCS = listOf(
Doc("Pragmatic Jhanas Guide", "v4.3", "jhanas-guide.pdf"),
Doc("Retreat Instructions", "v6.0.3 · Day 1 & 2", "retreat-instructions.pdf"),
)
@Composable
private fun ReadingTab() {
val context = LocalContext.current
Column(
Modifier
.fillMaxSize()
.padding(20.dp)
) {
DOCS.forEach { doc ->
Column(
Modifier
.fillMaxWidth()
.clickable { openPdf(context, doc) }
.background(Color.White.copy(alpha = 0.05f), RoundedCornerShape(12.dp))
.padding(20.dp)
) {
Text(doc.title, color = Color.White.copy(alpha = 0.9f), fontSize = 16.sp)
Spacer(Modifier.height(4.dp))
Text(doc.subtitle, color = Dim, fontSize = 12.sp)
}
Spacer(Modifier.height(12.dp))
}
Text(
"Opens in the system PDF viewer.",
color = Dim,
fontSize = 11.sp,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
}
}
private fun openPdf(context: Context, doc: Doc) {
try {
val dir = File(context.cacheDir, "reading").apply { mkdirs() }
val outFile = File(dir, doc.asset)
if (!outFile.exists() || outFile.length() == 0L) {
context.assets.open("reading/${doc.asset}").use { input ->
outFile.outputStream().use { input.copyTo(it) }
}
}
val uri = FileProvider.getUriForFile(context, "${context.packageName}.files", outFile)
context.startActivity(
Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, "application/pdf")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
)
} catch (e: Exception) {
Toast.makeText(context, "Could not open PDF: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
// ----------------------------------------------------------------- Dialogs
@Composable

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="reading" path="reading/" />
</paths>