@@ -58,6 +58,19 @@ import androidx.compose.ui.text.style.TextOverflow
5858import androidx.compose.ui.unit.dp
5959import androidx.compose.ui.unit.sp
6060import androidx.lifecycle.compose.collectAsStateWithLifecycle
61+ import android.content.Intent
62+ import android.net.Uri
63+ import androidx.compose.animation.fadeIn
64+ import androidx.compose.animation.fadeOut
65+ import androidx.compose.foundation.Image
66+ import androidx.compose.runtime.produceState
67+ import androidx.compose.ui.graphics.ImageBitmap
68+ import androidx.compose.ui.graphics.asImageBitmap
69+ import androidx.compose.ui.layout.ContentScale
70+ import androidx.compose.ui.platform.LocalContext
71+ import nz.aryan.akllive.data.Images
72+ import nz.aryan.akllive.data.Photo
73+ import nz.aryan.akllive.data.Photos
6174import nz.aryan.akllive.AppViewModel
6275import nz.aryan.akllive.data.BusModel
6376import nz.aryan.akllive.data.Fleet
@@ -158,35 +171,55 @@ private fun BusPortrait(model: BusModel?, modifier: Modifier, frameT: State<Floa
158171 }
159172}
160173
174+ /* *
175+ * A real photo of the model (from Wikimedia Commons, credited on it) over the
176+ * drawn bus, which shows until the photo's in, or if there isn't one.
177+ */
178+ @Composable
179+ private fun ModelPhoto (m : BusModel ? , modifier : Modifier , frameT : State <Float >? = null) {
180+ val ctx = LocalContext .current
181+ val photo by produceState<Photo ?>(null , m?.id) { value = m?.let { Photos .forModel(ctx, it) } }
182+ val bmp by produceState<ImageBitmap ?>(null , photo?.url) { value = photo?.url?.let { Images .load(ctx, it)?.asImageBitmap() } }
183+ Box (modifier) {
184+ BusPortrait (m, Modifier .fillMaxSize(), frameT)
185+ androidx.compose.animation.AnimatedVisibility (bmp != null , Modifier .fillMaxSize(), enter = fadeIn(), exit = fadeOut()) {
186+ bmp?.let { Image (it, m?.name, Modifier .fillMaxSize(), contentScale = ContentScale .Crop ) }
187+ }
188+ val p = photo
189+ if (bmp != null && p != null ) {
190+ Text (p.credit, color = Color .White , fontSize = 9 .sp, maxLines = 1 , overflow = TextOverflow .Ellipsis ,
191+ modifier = Modifier .align(Alignment .BottomStart ).fillMaxWidth()
192+ .background(Brush .verticalGradient(listOf (Color .Transparent , Color .Black .copy(alpha = 0.55f ))))
193+ .clickable { try { ctx.startActivity(Intent (Intent .ACTION_VIEW , Uri .parse(p.page))) } catch (_: Exception ) { } }
194+ .padding(start = 10 .dp, end = 10 .dp, top = 10 .dp, bottom = 4 .dp))
195+ }
196+ }
197+ }
198+
161199@OptIn(ExperimentalLayoutApi ::class )
162200@Composable
163201private fun ModelCard (m : BusModel , live : List <LiveBus >, click : () -> Unit ) {
164- Card (Modifier .fillMaxWidth().clickable(onClick = click), shape = RoundedCornerShape (22 .dp),
165- elevation = CardDefaults .cardElevation(2 .dp),
166- colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.surface)) {
167- Row (Modifier .padding(12 .dp), verticalAlignment = Alignment .CenterVertically ) {
168- BusPortrait (m, Modifier .size(width = 118 .dp, height = 74 .dp).clip(RoundedCornerShape (14 .dp)))
169- Spacer (Modifier .width(12 .dp))
170- Column (Modifier .weight(1f )) {
171- Text (m.name, fontWeight = FontWeight .Bold , style = MaterialTheme .typography.titleMedium,
172- maxLines = 2 , overflow = TextOverflow .Ellipsis )
173- Text (m.kind, style = MaterialTheme .typography.bodySmall, color = MaterialTheme .colorScheme.onSurfaceVariant)
174- Spacer (Modifier .height(6 .dp))
175- FlowRow (horizontalArrangement = Arrangement .spacedBy(4 .dp), verticalArrangement = Arrangement .spacedBy(4 .dp)) {
176- for ((op, _) in Fleet .numbers(m)) OperatorTag (op)
202+ Card (onClick = click, modifier = Modifier .fillMaxWidth(), shape = RoundedCornerShape (24 .dp),
203+ colors = CardDefaults .cardColors(containerColor = MaterialTheme .colorScheme.surfaceContainerLow)) {
204+ Box (Modifier .fillMaxWidth().height(150 .dp)) {
205+ ModelPhoto (m, Modifier .fillMaxSize())
206+ Row (Modifier .align(Alignment .TopEnd ).padding(10 .dp).background(Color .Black .copy(alpha = 0.55f ), RoundedCornerShape (50 ))
207+ .padding(horizontal = 10 .dp, vertical = 4 .dp), verticalAlignment = Alignment .CenterVertically ) {
208+ if (live.isNotEmpty()) {
209+ Box (Modifier .size(7 .dp).background(Pal .Live , CircleShape ))
210+ Spacer (Modifier .width(5 .dp))
177211 }
212+ Text (if (live.isEmpty()) " None out" else " ${live.size} out now" , color = Color .White , fontSize = 12 .sp,
213+ fontWeight = FontWeight .Bold )
178214 }
179- Spacer (Modifier .width(8 .dp))
180- Column (horizontalAlignment = Alignment .End ) {
181- Text (" ${live.size} " , fontWeight = FontWeight .Black , fontSize = 26 .sp,
182- color = if (live.isEmpty()) MaterialTheme .colorScheme.onSurfaceVariant else MaterialTheme .colorScheme.onSurface)
183- Row (verticalAlignment = Alignment .CenterVertically ) {
184- if (live.isNotEmpty()) {
185- Box (Modifier .size(7 .dp).background(Pal .Live , CircleShape ))
186- Spacer (Modifier .width(4 .dp))
187- }
188- Text (" out now" , style = MaterialTheme .typography.labelSmall, color = MaterialTheme .colorScheme.onSurfaceVariant)
189- }
215+ }
216+ Column (Modifier .padding(horizontal = 16 .dp, vertical = 12 .dp)) {
217+ Text (m.name, fontWeight = FontWeight .Bold , style = MaterialTheme .typography.titleMedium, maxLines = 1 ,
218+ overflow = TextOverflow .Ellipsis )
219+ Text (m.kind, style = MaterialTheme .typography.bodySmall, color = MaterialTheme .colorScheme.onSurfaceVariant)
220+ Spacer (Modifier .height(8 .dp))
221+ FlowRow (horizontalArrangement = Arrangement .spacedBy(6 .dp), verticalArrangement = Arrangement .spacedBy(6 .dp)) {
222+ for ((op, _) in Fleet .numbers(m)) OperatorTag (op)
190223 }
191224 }
192225 }
@@ -196,11 +229,12 @@ private fun ModelCard(m: BusModel, live: List<LiveBus>, click: () -> Unit) {
196229private fun OperatorTag (name : String ) {
197230 val code = Fleet .OPERATORS .entries.firstOrNull { it.value == name }?.key ? : " "
198231 val c = operatorColor(code)
199- Row (Modifier .background(c.copy(alpha = 0.14f ), RoundedCornerShape (50 )).padding(horizontal = 8 .dp, vertical = 2 .dp),
232+ Row (Modifier .background(MaterialTheme .colorScheme.surfaceContainerHighest, RoundedCornerShape (50 ))
233+ .padding(horizontal = 10 .dp, vertical = 4 .dp),
200234 verticalAlignment = Alignment .CenterVertically ) {
201- Box (Modifier .size(7 .dp).background(c, CircleShape ))
202- Spacer (Modifier .width(4 .dp))
203- Text (name, fontSize = 11 .sp, fontWeight = FontWeight . Bold , color = MaterialTheme .colorScheme.onSurface)
235+ Box (Modifier .size(8 .dp).background(c, CircleShape ))
236+ Spacer (Modifier .width(6 .dp))
237+ Text (name, style = MaterialTheme .typography.labelMedium , color = MaterialTheme .colorScheme.onSurface, maxLines = 1 )
204238 }
205239}
206240
@@ -249,8 +283,8 @@ private fun ModelScreen(vm: AppViewModel, id: String, all: List<LiveBus>, update
249283 val scroll = rememberScrollState()
250284
251285 Column (modifier.fillMaxSize().verticalScroll(scroll)) {
252- Box (Modifier .fillMaxWidth().height(200 .dp)) {
253- BusPortrait (model, Modifier .fillMaxSize(), frameT)
286+ Box (Modifier .fillMaxWidth().height(230 .dp)) {
287+ ModelPhoto (model, Modifier .fillMaxSize(), frameT)
254288 IconButton (onClick = close, modifier = Modifier .padding(8 .dp)
255289 .background(Color .Black .copy(alpha = 0.35f ), CircleShape )) {
256290 Icon (Icons .AutoMirrored .Filled .ArrowBack , " Back" , tint = Color .White )
0 commit comments