This commit is contained in:
2026-04-16 10:09:06 +01:00
parent 118c75dd6e
commit ebfa360e8b
53 changed files with 1334 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
package com.example.api
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.api.ui.components.CharacterItem
import com.example.api.ui.theme.ApiTheme
import com.example.api.viewmodel.CharacterViewModel
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ApiTheme {
CharacterScreen()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CharacterScreen(viewModel: CharacterViewModel = viewModel()) {
val characters by viewModel.characters.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
val error by viewModel.error.collectAsState()
Scaffold(
topBar = {
TopAppBar(title = { Text("Rick and Morty Characters") })
}
) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
when {
isLoading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
error != null -> {
Column(
modifier = Modifier.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = error ?: "Erro desconhecido")
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { viewModel.fetchCharacters() }) {
Text("Tentar novamente")
}
}
}
else -> {
LazyColumn {
items(characters) { character ->
CharacterItem(character = character)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
package com.example.api.model
import com.google.gson.annotations.SerializedName
data class CharacterResponse(
val results: List<Character>
)
data class Character(
val id: Int,
val name: String,
val status: String,
val species: String,
val image: String,
val location: Location
)
data class Location(
val name: String
)

View File

@@ -0,0 +1,16 @@
package com.example.api.network
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private const val BASE_URL = "https://rickandmortyapi.com/api/"
val apiService: RickAndMortyService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RickAndMortyService::class.java)
}
}

View File

@@ -0,0 +1,9 @@
package com.example.api.network
import com.example.api.model.CharacterResponse
import retrofit2.http.GET
interface RickAndMortyService {
@GET("character")
suspend fun getCharacters(): CharacterResponse
}

View File

@@ -0,0 +1,42 @@
package com.example.api.ui.components
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.example.api.model.Character
@Composable
fun CharacterItem(character: Character) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
shape = RoundedCornerShape(8.dp)
) {
Row(modifier = Modifier.padding(8.dp)) {
AsyncImage(
model = character.image,
contentDescription = null,
modifier = Modifier
.size(100.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = character.name, style = MaterialTheme.typography.titleLarge)
Text(text = "Status: ${character.status}")
Text(text = "Species: ${character.species}")
Text(text = "Location: ${character.location.name}")
}
}
}
}

View File

@@ -0,0 +1,11 @@
package com.example.api.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@@ -0,0 +1,58 @@
package com.example.api.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun ApiTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,34 @@
package com.example.api.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@@ -0,0 +1,39 @@
package com.example.api.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.api.model.Character
import com.example.api.network.RetrofitClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class CharacterViewModel : ViewModel() {
private val _characters = MutableStateFlow<List<Character>>(emptyList())
val characters: StateFlow<List<Character>> = _characters
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error
init {
fetchCharacters()
}
fun fetchCharacters() {
viewModelScope.launch {
_isLoading.value = true
_error.value = null
try {
val response = RetrofitClient.apiService.getCharacters()
_characters.value = response.results
} catch (e: Exception) {
_error.value = "Erro ao carregar personagens: ${e.localizedMessage}"
} finally {
_isLoading.value = false
}
}
}
}