trabalho
This commit is contained in:
87
app/src/main/java/com/example/api/MainActivity.kt
Normal file
87
app/src/main/java/com/example/api/MainActivity.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
app/src/main/java/com/example/api/model/Character.kt
Normal file
20
app/src/main/java/com/example/api/model/Character.kt
Normal 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
|
||||
)
|
||||
16
app/src/main/java/com/example/api/network/RetrofitClient.kt
Normal file
16
app/src/main/java/com/example/api/network/RetrofitClient.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
app/src/main/java/com/example/api/ui/theme/Color.kt
Normal file
11
app/src/main/java/com/example/api/ui/theme/Color.kt
Normal 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)
|
||||
58
app/src/main/java/com/example/api/ui/theme/Theme.kt
Normal file
58
app/src/main/java/com/example/api/ui/theme/Theme.kt
Normal 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
|
||||
)
|
||||
}
|
||||
34
app/src/main/java/com/example/api/ui/theme/Type.kt
Normal file
34
app/src/main/java/com/example/api/ui/theme/Type.kt
Normal 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
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user