This commit is contained in:
Xaver 2024-12-11 20:27:20 +01:00
parent 7d6dec2d4f
commit 133e06db1e
21 changed files with 573 additions and 72 deletions

View File

@ -1,6 +1,9 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.10"
id("com.google.devtools.ksp") version "1.6.21-1.0.6"
}
android {
@ -53,6 +56,17 @@ dependencies {
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
// Retrofit
implementation(libs.converter.kotlinx.serialization)
implementation(libs.retrofit)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@ -11,6 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GameLibraryApp"
android:name=".LibraryApplication"
tools:targetApi="31">
<activity
android:name=".MainActivity"

View File

@ -0,0 +1,28 @@
package at.xaxa.gamelibraryapp
import android.app.Application
import at.xaxa.gamelibraryapp.data.GameRepository
import at.xaxa.gamelibraryapp.data.db.LibraryDatabase
import at.xaxa.gamelibraryapp.data.remote.GameRemoteService
import kotlinx.serialization.json.Json
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
class LibraryApplication: Application() {
val libraryRepository by lazy {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.rawg.io/api/") // Only the root URL
.addConverterFactory(Json { ignoreUnknownKeys = true }
.asConverterFactory("application/json".toMediaType()))
.build()
val gameRemoteService = retrofit.create(GameRemoteService::class.java)
GameRepository(
LibraryDatabase.getDatabase(this).libraryDao(),
gameRemoteService
)
}
}

View File

@ -11,6 +11,8 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import at.xaxa.gamelibraryapp.ui.LibraryApp
import at.xaxa.gamelibraryapp.ui.theme.GameLibraryAppTheme
class MainActivity : ComponentActivity() {
@ -20,8 +22,7 @@ class MainActivity : ComponentActivity() {
setContent {
GameLibraryAppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
LibraryApp(
modifier = Modifier.padding(innerPadding)
)
}
@ -29,19 +30,3 @@ class MainActivity : ComponentActivity() {
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
GameLibraryAppTheme {
Greeting("Android")
}
}

View File

@ -0,0 +1,8 @@
package at.xaxa.gamelibraryapp.data
data class Game(
val id: Int,
val name: String,
val details: String,
val note: String
)

View File

@ -0,0 +1,43 @@
package at.xaxa.gamelibraryapp.data
import at.xaxa.gamelibraryapp.data.db.LibraryDao
import at.xaxa.gamelibraryapp.data.db.LibraryEntity
import at.xaxa.gamelibraryapp.data.remote.GameRemoteService
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GameRepository(private val libraryDao: LibraryDao, private val gameRemoteService: GameRemoteService) {
private val apiKey = "7a2a800a085d41e88621a8a59dc5ea82"
/*suspend fun loadInitialContacts() {
try {
val contactDtoList = gameRemoteService.getAllContacts()
contactDtoList.map {
Game(0, it., "${it.telephoneNumber}", it.age)
}.forEach {
insertContact(it)
}
} catch (e: Exception) {
Log.e("Repository", "Something went wrong! ${e.message}", e)
}
}*/
fun getAllGamesInLibrary(): Flow<List<Game>> {
return libraryDao.getAllEntries().map {
it.map {item -> Game(item._id, item.name, item.details, item.note) }
}
}
suspend fun findGameById(id: Int): LibraryEntity {
val item = libraryDao.findEntryById(id)
return LibraryEntity(item._id, item.name, item.details, item.note)
}
suspend fun addEntry(libraryEntity: LibraryEntity) {
libraryDao.addEntry(LibraryEntity(libraryEntity._id, libraryEntity.name, libraryEntity.details, libraryEntity.note))
}
suspend fun updateEntry(game: LibraryEntity) {
libraryDao.updateEntry(LibraryEntity(game._id, game.name, game.details, game.note))
}
}

View File

@ -0,0 +1,30 @@
package at.xaxa.gamelibraryapp.data.db
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import at.xaxa.gamelibraryapp.data.Game
import kotlinx.coroutines.flow.Flow
@Dao
interface LibraryDao {
@Insert
suspend fun addEntry(libraryEntity: LibraryEntity)
@Update
suspend fun updateEntry(libraryEntity: LibraryEntity)
@Delete
suspend fun deleteEntry(libraryEntity: LibraryEntity)
@Query("SELECT * FROM library WHERE _id = :id")
suspend fun findEntryById(id: Int): LibraryEntity
@Query("SELECT * FROM library")
fun getAllEntries(): Flow<List<LibraryEntity>>
@Query("SELECT * FROM library WHERE name = :entryName")
fun getEntriesWithName(entryName: String): Flow<List<LibraryEntity>>
}

View File

@ -0,0 +1,32 @@
package at.xaxa.gamelibraryapp.data.db
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import at.xaxa.gamelibraryapp.LibraryApplication
@Database(entities = [LibraryEntity::class], version = 2)
abstract class LibraryDatabase : RoomDatabase() {
abstract fun libraryDao(): LibraryDao
companion object {
@Volatile
private var Instance: LibraryDatabase? = null
fun getDatabase(context: LibraryApplication): LibraryDatabase {
// if the Instance is not null, return it, otherwise create a new database instance.
return Instance ?: synchronized(this) {
val instance = Room.databaseBuilder(context, LibraryDatabase::class.java, "library_database")
/**
* Setting this option in your app's database builder means that Room
* permanently deletes all data from the tables in your database when it
* attempts to perform a migration with no defined migration path.
*/
.fallbackToDestructiveMigration()
.build()
Instance = instance
return instance
}
}
}
}

View File

@ -0,0 +1,13 @@
package at.xaxa.gamelibraryapp.data.db
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "library")
data class LibraryEntity(
@PrimaryKey
val _id: Int,
val name: String,
val details: String,
val note: String
)

View File

@ -0,0 +1,25 @@
package at.xaxa.gamelibraryapp.data.remote
import kotlinx.serialization.Serializable
@Serializable
data class ApiResponse(
val count: Int,
val next: String?,
val previous: String?,
val results: List<GameDto>
)
@Serializable
data class GameDto(
val id: Int,
val name: String,
val background_image: String,
val genres: List<GenreDto>
)
@Serializable
data class GenreDto(
val id: Int,
val name: String
)

View File

@ -0,0 +1,22 @@
package at.xaxa.gamelibraryapp.data.remote
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
// TODO Implement Remote Service
interface GameRemoteService {
@GET("games")
suspend fun getAllGames(@Query("key") apiKey: String): List<ApiResponse>
@GET("contacts/{contactId}")
suspend fun getContactById(@Path("contactId") contactId: Int)
@GET("contacts/search") // becomes contacts/search?filter=<content of filterText>
suspend fun findContacts(@Query("filter") filterText : String): List<ApiResponse>
@POST("contacts")
suspend fun addContact(@Body contactDto: ApiResponse)
}

View File

@ -0,0 +1,28 @@
package at.xaxa.gamelibraryapp.ui
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import at.xaxa.gamelibraryapp.LibraryApplication
import at.xaxa.gamelibraryapp.ui.Details.DetailsViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
DetailsViewModel(this.createSavedStateHandle(), (this[APPLICATION_KEY] as LibraryApplication).libraryRepository)
}
/*initializer {
ContactsViewModel((this[APPLICATION_KEY] as ContactsApplication).contactsRepository)
}
initializer {
ContactDetailsViewModel(this.createSavedStateHandle(), (this[APPLICATION_KEY] as ContactsApplication).contactsRepository)
}
initializer {
ContactEditViewModel(this.createSavedStateHandle(), (this[APPLICATION_KEY] as ContactsApplication).contactsRepository)
}*/
}
}

View File

@ -0,0 +1,25 @@
package at.xaxa.gamelibraryapp.ui.Details
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import at.xaxa.gamelibraryapp.ui.AppViewModelProvider
import at.xaxa.gamelibraryapp.ui.DetailCard
@Composable
fun DetailView(
navBackStackEntry: NavBackStackEntry,
viewModel: DetailsViewModel = viewModel(factory = AppViewModelProvider.Factory),
modifier: Modifier = Modifier
) {
// Extract arguments here
val gameId = navBackStackEntry.arguments?.getInt("gameId") // Or `Int` if needed
DetailCard(modifier,
title = "Game Title $gameId", // Use the extracted gameId
details = "details",
imageUrl = "https://media.rawg.io/media/games/b7f/b7ffc4c4776e61eca19d36d3c227f89a.jpg",
note = "note"
)
}

View File

@ -0,0 +1,45 @@
package at.xaxa.gamelibraryapp.ui.Details
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.xaxa.gamelibraryapp.data.GameRepository
import at.xaxa.gamelibraryapp.data.db.LibraryEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
data class GameEditUi(
val libraryEntity: LibraryEntity = LibraryEntity(0, "", "", "")
)
class DetailsViewModel(private val savedStateHandle: SavedStateHandle, private val gameRepository: GameRepository) : ViewModel() {
private val gameId: Int = checkNotNull(savedStateHandle["gameId"])
var editUiState by mutableStateOf(GameEditUi())
private set
init {
viewModelScope.launch {
val game = withContext(Dispatchers.IO) {
gameRepository.findGameById(gameId)
}
editUiState = GameEditUi(game)
}
}
fun updateGame(libraryEntity: LibraryEntity) {
editUiState = editUiState.copy(libraryEntity=libraryEntity)
}
fun saveLibrary() {
viewModelScope.launch {
gameRepository.updateEntry(editUiState.libraryEntity)
}
}
}

View File

@ -0,0 +1,38 @@
package at.xaxa.gamelibraryapp.ui
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.xaxa.gamelibraryapp.data.Game
import at.xaxa.gamelibraryapp.data.GameRepository
import at.xaxa.gamelibraryapp.data.db.LibraryEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
data class GameDetailUi(
val game: LibraryEntity = LibraryEntity(0, "", "", "")
)
class GameDetailsViewModel(savedStateHandle: SavedStateHandle, private val gameRepository: GameRepository): ViewModel() {
private val gameId: Int = checkNotNull(savedStateHandle["gameId"])
private val _detailUiState = MutableStateFlow(GameDetailUi())
val detailUiState = _detailUiState.asStateFlow()
init {
viewModelScope.launch {
val libraryEntity = withContext(Dispatchers.IO) {
gameRepository.findGameById(gameId)
}
_detailUiState.update {
GameDetailUi(libraryEntity)
}
}
}
}

View File

@ -0,0 +1,49 @@
package at.xaxa.gamelibraryapp.ui.GameList
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import at.xaxa.gamelibraryapp.ui.HorizontalCard
@Composable
fun GameList(modifier: Modifier = Modifier, onCardClick: (Int) -> Unit) {
var searchText by remember { mutableStateOf("") }
Column(modifier = modifier) {
// Search Bar
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("Search games") },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp)
)
// Game List
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(16) { index ->
HorizontalCard(
modifier = modifier,
title = "Game $index",
details = "Details about game $index",
imageUrl = "https://media.rawg.io/media/games/b7f/b7ffc4c4776e61eca19d36d3c227f89a.jpg",
onClick = {
onCardClick(index)
}
)
}
}
}
}

View File

@ -0,0 +1,2 @@
package at.xaxa.gamelibraryapp.ui.GameList

View File

@ -1,47 +1,32 @@
package at.xaxa.gamelibraryapp.ui
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.res.painterResource
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -53,56 +38,70 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import at.xaxa.gamelibraryapp.R
import at.xaxa.gamelibraryapp.ui.Details.DetailView
import at.xaxa.gamelibraryapp.ui.GameList.GameList
import at.xaxa.gamelibraryapp.ui.Search.SearchList
import coil3.compose.AsyncImage
import java.time.format.TextStyle
enum class DemoRoutes(val route: String) {
enum class LibraryRoutes(val route: String) {
List("list"),
Add("add"),
Detail("task/{taskId}")
Detail("game/{gameId}")
}
@Composable
fun LibraryApp(modifier: Modifier = Modifier) {
val navController = rememberNavController()
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
Scaffold(
bottomBar = {
androidx.compose.material3.NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") },
label = { Text("List") },
selected = navController.currentBackStackEntryAsState().value?.destination?.route == "list",
onClick = { navController.navigate(DemoRoutes.List.route) }
)
NavigationBarItem(
icon = { Icon(Icons.Default.Add, contentDescription = "Add") },
label = { Text("Add") },
selected = navController.currentBackStackEntryAsState().value?.destination?.route == "add",
onClick = { navController.navigate(DemoRoutes.Add.route) }
)
// Hide bottom bar only when in the Detail view
if (currentRoute != LibraryRoutes.Detail.route) {
androidx.compose.material3.NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") },
label = { Text("List") },
selected = currentRoute == LibraryRoutes.List.route,
onClick = { navController.navigate(LibraryRoutes.List.route) }
)
NavigationBarItem(
icon = { Icon(Icons.Default.Add, contentDescription = "Add") },
label = { Text("Search") },
selected = currentRoute == LibraryRoutes.Add.route,
onClick = { navController.navigate(LibraryRoutes.Add.route) }
)
}
}
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = DemoRoutes.List.route,
modifier = Modifier.padding(innerPadding)
startDestination = LibraryRoutes.List.route,
modifier = Modifier.padding(innerPadding),
contentAlignment = Alignment.Center
) {
composable(DemoRoutes.List.route){
// screen
composable(LibraryRoutes.List.route){
GameList(onCardClick = {
navController.navigate("game/$it")
})
}
composable(DemoRoutes.Add.route) {
// screen
composable(LibraryRoutes.Add.route) {
SearchList(onCardClick = {
navController.navigate("game/$it")
})
}
composable(
route = DemoRoutes.Detail.route,
arguments = listOf(navArgument("taskId") {
route = LibraryRoutes.Detail.route,
arguments = listOf(navArgument("gameId") {
type = NavType.IntType
})
) {
// screen
backStackEntry ->
DetailView(
navBackStackEntry = backStackEntry,
modifier = Modifier
)
}
}
}
@ -110,23 +109,93 @@ fun LibraryApp(modifier: Modifier = Modifier) {
@Composable
fun HorizontalCard(modifier: Modifier = Modifier, title: String, details: String) {
fun DetailCard(
modifier: Modifier = Modifier,
title: String,
details: String,
note: String,
imageUrl: String) {
Surface(
modifier = modifier
.requiredWidth(360.dp)
.requiredHeight(80.dp)
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp)
.clip(RoundedCornerShape(12.dp))
.border(1.dp, Color.LightGray, RoundedCornerShape(12.dp))
) {
Row(modifier = Modifier.fillMaxSize()) {
LayoutMediaText(modifier = Modifier.weight(1f), title, details)
ShowPicture(modifier = Modifier.weight(0.5f))
Column(modifier) {
ShowPicture(modifier = Modifier.height(200.dp), imageUrl)
LayoutMediaText(modifier = Modifier, title, details)
}
}
}
@Composable
fun HorizontalCard(
modifier: Modifier = Modifier,
title: String,
details: String,
imageUrl: String,
onClick: () -> Unit) {
Surface(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp)
.requiredHeight(80.dp)
.clip(RoundedCornerShape(12.dp))
.border(1.dp, Color.LightGray, RoundedCornerShape(12.dp))
.clickable { onClick() }
) {
Row(modifier = Modifier.fillMaxSize()) {
LayoutMediaTextHor(modifier = Modifier.weight(1f), title, details)
ShowPicture(modifier = Modifier.weight(0.5f), imageUrl)
}
}
}
@Composable
fun LayoutMediaText(modifier: Modifier = Modifier, title: String, details: String) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.clip(shape = RoundedCornerShape(12.dp))
) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.Start),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(all = 16.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.Top),
modifier = Modifier
) {
Text(
text = title,
color = Color.DarkGray,
lineHeight = 1.5.em,
style = androidx.compose.ui.text.TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
letterSpacing = 0.15.sp
),
modifier = Modifier
.fillMaxWidth())
Text(
text = details,
color = Color.DarkGray,
lineHeight = 1.43.em,
style = androidx.compose.ui.text.TextStyle(
fontSize = 14.sp,
letterSpacing = 0.25.sp
))
}
}
}
}
@Composable
fun LayoutMediaTextHor(modifier: Modifier = Modifier, title: String, details: String) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
@ -173,7 +242,7 @@ fun LayoutMediaText(modifier: Modifier = Modifier, title: String, details: Strin
}
@Composable
fun ShowPicture(modifier: Modifier = Modifier) {
fun ShowPicture(modifier: Modifier = Modifier, imageUrl: String) {
Surface(
shape = RoundedCornerShape(12.dp),
color = Color(0xfffef7ff),
@ -182,9 +251,10 @@ fun ShowPicture(modifier: Modifier = Modifier) {
) {
//Display Image here
AsyncImage(
model = "https://media.rawg.io/media/games/b7f/b7ffc4c4776e61eca19d36d3c227f89a.jpg",
model = imageUrl,
contentDescription = "Translated description of what the image contains",
Modifier.fillMaxHeight()
Modifier.fillMaxHeight(),
contentScale = ContentScale.Crop
)
}
}
@ -192,5 +262,13 @@ fun ShowPicture(modifier: Modifier = Modifier) {
@Preview(widthDp = 360, heightDp = 80)
@Composable
private fun HorizontalCardPreview() {
HorizontalCard(Modifier, "Uncharted 3", "Action Adventure, 2011")
HorizontalCard(Modifier, "Uncharted 3", "Action Adventure, 2011", "https://media.rawg.io/media/games/b7f/b7ffc4c4776e61eca19d36d3c227f89a.jpg", onClick = {
println("clicked")
})
}
@Preview
@Composable
private fun DetailCardPreview() {
DetailCard(Modifier, "Uncharted 3", "Action Adventure, 2011", "test", "https://media.rawg.io/media/games/b7f/b7ffc4c4776e61eca19d36d3c227f89a.jpg")
}

View File

@ -0,0 +1,22 @@
package at.xaxa.gamelibraryapp.ui.Search
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import at.xaxa.gamelibraryapp.ui.HorizontalCard
@Composable
fun SearchList(modifier: Modifier = Modifier, onCardClick: (Int) -> Unit){
LazyColumn{
items(16) { index ->
HorizontalCard(modifier,
"test $index",
"details $index",
"https://media.rawg.io/media/games/6e0/6e0c19bb111bd4fa20cf0eb72a049519.jpg",
onClick = {
onCardClick(index)
}
)
}
}
}

View File

@ -0,0 +1,2 @@
package at.xaxa.gamelibraryapp.ui.Search

View File

@ -1,20 +1,27 @@
[versions]
agp = "8.6.0"
coilCompose = "3.0.4"
converterKotlinxSerialization = "2.11.0"
kotlin = "1.9.0"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
kotlinxSerializationJson = "1.6.3"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3"
composeBom = "2024.04.01"
navigationCompose = "2.8.4"
roomRuntime = "2.6.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilCompose" }
converter-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "converterKotlinxSerialization" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@ -29,6 +36,8 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterKotlinxSerialization" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }