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>(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, 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) } } }