Detail Page Design using Jetpack Compose

Hi please check with my previous article of RecylerView Using Jetpack Compose

in the below link which is the continuos of the previous post

in this post we will be creating a detail page from the recylerview selection

First lets create a ProfileScreen.kt inside the ProfileScreen class we will be craeting a composable function ProfileScreen taking Puppy data class as an argument and please check with below program i mention what the lined used for in double slash

@Composable
fun ProfileScreen(puppy: Puppy) {
val scrollState = rememberScrollState()

//Column-->covering the maximum width and height on the screen.
Column
(modifier = Modifier.fillMaxSize()) {

//BoxWithConstraints-->This is used to help us pass the height of the screen to our content in order for us to adjust and size our content on the screen accordingly

BoxWithConstraints
{
//Surface-->This will be used to style our content as defined by material design you know, text color, text size, etc

Surface
{
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState),
) {
// ProfileHeader which contain the image part

ProfileHeader
(
puppy,
this@BoxWithConstraints.maxHeight
)
// Contain the text of age,title,sex
ProfileContent
(puppy = puppy,this@BoxWithConstraints.maxHeight)
}
//Flaoting Buttom at the bottom of screen

AdoptFab
(
extended = scrollState.value == 0,
modifier = Modifier.align(Alignment.BottomEnd)
)
}
}
}
}

lets create a composable function ProfileHeader

@Composable
private fun ProfileHeader(
puppy: Puppy,
containerHeight: Dp
) {
Image(
modifier = Modifier
.heightIn(max = containerHeight / 2)
.fillMaxWidth(),
//The painter argument to let us set the image using the image resource id.
painter = painterResource(id = puppy.puppyImageId),
//The contentScale to set the aspect ratio of the image so it neither looks stretched,nor let the image take its original width and height
contentScale = ContentScale.Crop,
//The contentDescription argument to define what the image represents for accessibility purposes
contentDescription = null
)
}

lets create a composable function ProfileContent

@Composable
private fun ProfileContent(puppy: Puppy, containerHeight: Dp) {
Column {
Title(puppy)
ProfileProperty("Sex", puppy.sex)
ProfileProperty("Age", puppy.age.toString())
ProfileProperty("Personality", puppy.description)
// To make the view scrollable
Spacer
(Modifier.height((containerHeight - 320.dp).coerceAtLeast(0.dp)))
}
}

lets create a composable function Title which place the title below the image

@Composable
private fun Title(
puppy: Puppy
) {
Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp, top = 16.dp)) {
Text(
text = puppy.title,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
,fontFamily = italicrobofonts
)
}
}

lets create a composable function ProfileProperty which place the subheading below the title with divider

@Composable
fun ProfileProperty(label: String, value: String) {
Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)) {
//Divider function to add a vertical line before showing any new property
Divider(modifier = Modifier.padding(bottom = 4.dp))
Text(
text = label,
modifier = Modifier.height(24.dp),
style = MaterialTheme.typography.caption,
fontFamily = noromalrobofonts
)
Text(
text = value,
modifier = Modifier.height(24.dp),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Visible,
fontFamily = noromalrobofonts
)
}
}

and finally we add our floating button

@Composable
fun AdoptFab(extended: Boolean, modifier: Modifier = Modifier) {
FloatingActionButton(
onClick = { /* TODO */ },
modifier = modifier
.padding(16.dp)
.padding()
.height(48.dp)
.widthIn(min = 48.dp),
backgroundColor = Purple200,
contentColor = Color.White
) {
Icon(Icons.Filled.Add,"")
}
}

so our final class of ProfileScreen.kt will look like

package com.example.myapplication.presentation.detailpage

import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Divider
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Call
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.myapplication.domain.model.Puppy
import com.example.myapplication.ui.theme.Purple200
import com.example.myapplication.ui.theme.italicrobofonts
import com.example.myapplication.ui.theme.noromalrobofonts

@Composable
fun ProfileScreen(puppy: Puppy) {
val scrollState = rememberScrollState()

//Column-->covering the maximum width and height on the screen.
Column(modifier = Modifier.fillMaxSize()) {

//BoxWithConstraints-->This is used to help us pass the height of the screen to our content
// in order for us to adjust and size our content on the screen accordingly
BoxWithConstraints {
//Surface-->This will be used to style our content as defined by material design
// , you know, text color, text size, etc
Surface
{
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState),
) {
// ProfileHeader which contain the image part
ProfileHeader(
puppy,
this@BoxWithConstraints.maxHeight
)
// Contain the text of age,title,sex
ProfileContent
(puppy = puppy,this@BoxWithConstraints.maxHeight)
}
//Flaoting Buttom at the bottom of screen
AdoptFab
(
extended = scrollState.value == 0,
modifier = Modifier.align(Alignment.BottomEnd)
)
}
}
}
}

@Composable
private fun ProfileContent(puppy: Puppy, containerHeight: Dp) {
Column {
Title(puppy)
ProfileProperty("Sex", puppy.sex)
ProfileProperty("Age", puppy.age.toString())
ProfileProperty("Personality", puppy.description)
// To make the view scrollable
Spacer
(Modifier.height((containerHeight - 320.dp).coerceAtLeast(0.dp)))
}
}

@Composable
private fun ProfileHeader(
puppy: Puppy,
containerHeight: Dp
) {
Image(
modifier = Modifier
.heightIn(max = containerHeight / 2)
.fillMaxWidth(),
//The painter argument to let us set the image using the image resource id.
painter = painterResource(id = puppy.puppyImageId),
//The contentScale to set the aspect ratio of the image so it neither looks stretched,
// nor let the image take its original width and height
contentScale = ContentScale.Crop,
//The contentDescription argument to define what the
// image represents for accessibility purposes
contentDescription = null
)
}

@Composable
private fun Title(
puppy: Puppy
) {
Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp, top = 16.dp)) {
Text(
text = puppy.title,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
,fontFamily = italicrobofonts
)
}
}
@Composable
fun ProfileProperty(label: String, value: String) {
Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)) {
//Divider function to add a vertical line before showing any new property
Divider(modifier = Modifier.padding(bottom = 4.dp))
Text(
text = label,
modifier = Modifier.height(24.dp),
style = MaterialTheme.typography.caption,
fontFamily = noromalrobofonts
)
Text(
text = value,
modifier = Modifier.height(24.dp),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Visible,
fontFamily = noromalrobofonts
)
}
}

@Composable
fun AdoptFab(extended: Boolean, modifier: Modifier = Modifier) {
FloatingActionButton(
onClick = { /* TODO */ },
modifier = modifier
.padding(16.dp)
.padding()
.height(48.dp)
.widthIn(min = 48.dp),
backgroundColor = Purple200,
contentColor = Color.White
) {
Icon(Icons.Filled.Add,"")
}
}

Then we will be creating a ProfileActivity.kt class inside that we be calling our ProfileScreen(puppy = puppy)

package com.example.myapplication.presentation.detailpage

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import com.example.myapplication.domain.model.Puppy
import com.example.myapplication.presentation.Recylerviewdatas
import com.example.myapplication.ui.theme.MyApplicationTheme

class ProfileActivity : ComponentActivity() {

private val puppy: Puppy by lazy {
intent?.getSerializableExtra(PUPPY_ID) as Puppy
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(color = MaterialTheme.colors.background) {
// A surface container using the 'background' color from the theme
ProfileScreen(puppy = puppy)
}
}
}
}

companion object {
private const val PUPPY_ID = "puppy_id"
fun newIntent(context: Context, puppy: Puppy) =
Intent(context, ProfileActivity::class.java).apply {
putExtra(PUPPY_ID, puppy)
}
}
}

Then from our MainActivity.kt we call as

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplication.domain.model.Puppy
import com.example.myapplication.presentation.detailpage.ProfileActivity

import com.example.myapplication.presentation.viewpuppies.BarkHomeContent
import com.example.myapplication.ui.theme.MyApplicationTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Recylerviewdatas{
startActivity(ProfileActivity.newIntent(this, it))
}
}
}
}
}
}


@Composable
fun Recylerviewdatas(navigateToProfile: (Puppy) -> Unit) {
Scaffold( //implement the basic material design visual layout structure in compose.
content = {
BarkHomeContent(navigateToProfile = navigateToProfile)
}
)
}

followed by full code of BarkHome.kt

package com.example.myapplication.presentation.viewpuppies

import android.graphics.Paint
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import com.example.myapplication.data.data_source.DataProvider
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Face
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp
import com.example.myapplication.domain.model.Puppy
import com.example.myapplication.ui.theme.italicrobofonts
import com.example.myapplication.ui.theme.noromalrobofonts


@Composable
fun BarkHomeContent(navigateToProfile: (Puppy) -> Unit) {

// the remeber function in Composable function stores the current state of the variable
// in our case its puppies variable if the puppyList changes it will maintain the current state.
val puppies = remember { DataProvider.puppyList }

//This was the recylerview that we as an android developer are quite familar with it.
LazyColumn
(
// we are providing space by using padding
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) {
// the items function - that takes puppies item as our first params
// itemContent - that take our list item and populate with each item in list
items
(
items = puppies,
itemContent = {
PuppyListItem(puppy = it,navigateToProfile)
})
}
}

@Composable
fun PuppyListItem(puppy: Puppy, navigateToProfile: (Puppy) -> Unit) {
Box(modifier = Modifier)
{
Card(modifier = Modifier
.padding(horizontal = 8.dp, vertical = 8.dp)
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(corner = CornerSize(16.dp))
)
{
//Row - represent row in a list
Row {
//Inside row create coloumn of two text that take puppy title and view details
// as the second text
PuppyImage
(puppy)
Column (modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.align(Alignment.CenterVertically))
{
Row (modifier = Modifier
.fillMaxWidth()
)
{
Text(text = puppy.title,Modifier.weight(1f), color=Color.Black,fontSize = 15.sp,fontFamily = italicrobofonts)
Text(text = "Show Data",color=Color.Blue,fontSize = 12.sp,style = TextStyle(textDecoration = TextDecoration.Underline), fontFamily = italicrobofonts)

}

Row (modifier = Modifier
.clickable { navigateToProfile(puppy) }
.fillMaxWidth()
)
{

Text(text = puppy.description,
modifier = Modifier.weight(1f)
.padding(end = 10.dp),
color=Color.Gray,maxLines = 3,
fontSize = 14.sp,overflow = TextOverflow.Ellipsis,
fontFamily = noromalrobofonts,
)
}
}
}
}
IconButton(
onClick = { Unit },
modifier = Modifier.align(Alignment.BottomEnd)
.padding(bottom = 10.dp,top = 10.dp)
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete note",
tint = MaterialTheme.colors.onSurface
)
}
}


}





@Composable
private fun PuppyImage(puppy: Puppy) {
Image(
painter = painterResource(id = puppy.puppyImageId),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
}

Once you compiled the full code you can see output like