Files
WhosThat/app/src/main/java/space/bloggin/whosthat/HomeScreen.kt
T

169 lines
9.5 KiB
Kotlin

package space.bloggin.whosthat
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
@Composable
fun HomeScreen(store: Store, onOpen: (Tmdb.Hit) -> Unit, onAbout: () -> Unit) {
var q by rememberSaveable { mutableStateOf("") }
var results by remember { mutableStateOf<List<Tmdb.Hit>>(emptyList()) }
var error by remember { mutableStateOf("") }
var loading by remember { mutableStateOf(false) }
var recent by remember { mutableStateOf(store.recent()) }
var filter by rememberSaveable { mutableStateOf("all") }
LaunchedEffect(q) {
if (q.trim().length < 2) { results = emptyList(); error = ""; return@LaunchedEffect }
delay(350)
loading = true
try { results = Tmdb.search(q.trim()); error = "" } catch (e: Exception) { error = e.message ?: "Couldn't reach TMDB." }
loading = false
}
Column(Modifier.fillMaxSize().imePadding()) {
Row(Modifier.fillMaxWidth().padding(start = 16.dp, end = 6.dp, top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
Text("WHO'S ", color = Color.White, fontSize = 26.sp, fontWeight = FontWeight.Black)
Text("THAT", color = Accent, fontSize = 26.sp, fontWeight = FontWeight.Black)
Text("?", color = Color.White, fontSize = 26.sp, fontWeight = FontWeight.Black, modifier = Modifier.weight(1f))
Box(Modifier.size(48.dp).clickable(onClick = onAbout), contentAlignment = Alignment.Center) {
Text("☰", color = Color.White, fontSize = 24.sp)
}
}
OutlinedTextField(
value = q, onValueChange = { q = it },
modifier = Modifier.fillMaxWidth().padding(16.dp),
placeholder = { Text(if (filter == "person") "Type an actor's name" else "What are you watching? (or an actor's name)", color = Dim) },
singleLine = true, shape = Square,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
trailingIcon = {
if (q.isNotEmpty()) Box(Modifier.size(44.dp).clickable { q = "" }, contentAlignment = Alignment.Center) {
Text("✕", color = Dim, fontSize = 18.sp)
}
},
colors = OutlinedTextFieldDefaults.colors(focusedBorderColor = Accent, unfocusedBorderColor = Line, cursorColor = Accent)
)
Row(Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 10.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Chip("All", filter == "all") { filter = "all" }
Chip("Shows & Movies", filter == "titles") { filter = "titles" }
Chip("Actors", filter == "person") { filter = "person" }
}
val shown = when (filter) {
"person" -> results.filter { it.type == "person" }
"titles" -> results.filter { it.type != "person" }
else -> results
}
when {
q.trim().length >= 2 -> {
if (error.isNotBlank()) Text(error, color = Color(0xFFFF6B6B), modifier = Modifier.padding(16.dp))
if (loading && results.isEmpty()) Loading()
else if (shown.isEmpty() && !loading) Text(
if (filter == "person") "No actors match that name." else "Nothing matches that.",
color = Dim, fontSize = 16.sp, modifier = Modifier.padding(16.dp))
else HitList(shown, onOpen)
}
recent.isNotEmpty() -> {
Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
Text("RECENT", color = Dim, fontSize = 13.sp, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
Text("Clear", color = Dim, fontSize = 13.sp, modifier = Modifier.clickable { store.clearRecent(); recent = emptyList() }.padding(8.dp))
}
HitList(recent, onOpen)
}
else -> Column(Modifier.fillMaxSize().padding(28.dp).verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Text("Type the name of the movie or show you're watching.", color = Color.White, fontSize = 18.sp)
Spacer(Modifier.height(10.dp))
Text("Then tap the face you're wondering about to see who it is and where else you've seen them.", color = Dim, fontSize = 16.sp)
Spacer(Modifier.height(18.dp))
Text("Know the actor but not the show? Tap Actors, type their name, and choose Newest first to see what they're in right now.", color = Dim, fontSize = 16.sp)
}
}
}
}
@Composable
private fun HitList(list: List<Tmdb.Hit>, onOpen: (Tmdb.Hit) -> Unit) {
LazyColumn(Modifier.fillMaxSize()) {
items(list, key = { "${it.type}:${it.id}" }) { h ->
Row(Modifier.fillMaxWidth().clickable { onOpen(h) }.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically) {
Pic(Tmdb.img(h.poster, "w154"), h.title, Modifier.width(56.dp).height(84.dp))
Spacer(Modifier.width(14.dp))
Column(Modifier.weight(1f)) {
Text(h.title + if (h.year.isNotBlank()) " (${h.year})" else "", color = Color.White,
fontSize = 17.sp, fontWeight = FontWeight.Bold, maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(h.sub, color = if (h.type == "person") Accent else Dim, fontSize = 14.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
Box(Modifier.fillMaxWidth().height(1.dp).padding(horizontal = 16.dp).background(Line))
}
}
}
@Composable
fun AboutScreen(store: Store, familyBuild: Boolean, onBack: () -> Unit, onChangeKey: () -> Unit) {
val ctx = LocalContext.current
Column(Modifier.fillMaxSize()) {
TopBar("About", onBack = onBack)
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(20.dp)) {
Text("Who's That", color = Accent, fontSize = 26.sp, fontWeight = FontWeight.Bold)
Text("Version ${BuildConfig.VERSION_NAME}" + if (familyBuild) " (family edition)" else "", color = Dim, fontSize = 14.sp)
Spacer(Modifier.height(18.dp))
Text("Search for what you're watching, pick the season and episode for TV shows, and tap any face to see who it is, how old they are, and everything else they've been in. " +
"Mark shows and movies as watched, and those will always be listed first so you can spot where you know someone from.",
color = Color.White, fontSize = 16.sp, lineHeight = 22.sp)
Spacer(Modifier.height(22.dp))
Column(Modifier.fillMaxWidth().background(Panel).border(1.dp, Line).padding(16.dp)) {
Text("Data from TMDB", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(Modifier.height(6.dp))
Text("This product uses the TMDB API but is not endorsed or certified by TMDB.", color = Dim, fontSize = 14.sp)
Spacer(Modifier.height(10.dp))
SecondaryBtn("Visit themoviedb.org") { ctx.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://www.themoviedb.org"))) }
}
Spacer(Modifier.height(22.dp))
Text("You have ${store.watched().size} titles marked as watched.", color = Dim, fontSize = 15.sp)
Spacer(Modifier.height(22.dp))
SecondaryBtn(if (familyBuild && store.cred.isBlank()) "Use my own TMDB key instead" else "Change my TMDB key", Modifier.fillMaxWidth(), onClick = onChangeKey)
}
}
}