169 lines
9.5 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|