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 { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) 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 { android {
@ -53,6 +56,17 @@ dependencies {
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp) 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.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@ -11,6 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.GameLibraryApp" android:theme="@style/Theme.GameLibraryApp"
android:name=".LibraryApplication"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".MainActivity" 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.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview 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 import at.xaxa.gamelibraryapp.ui.theme.GameLibraryAppTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -20,8 +22,7 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
GameLibraryAppTheme { GameLibraryAppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting( LibraryApp(
name = "Android",
modifier = Modifier.padding(innerPadding) 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 package at.xaxa.gamelibraryapp.ui
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background 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.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight 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.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.Add 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.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.NavigationBarItem 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.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color 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.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -53,56 +38,70 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument 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 coil3.compose.AsyncImage
import java.time.format.TextStyle
enum class DemoRoutes(val route: String) { enum class LibraryRoutes(val route: String) {
List("list"), List("list"),
Add("add"), Add("add"),
Detail("task/{taskId}") Detail("game/{gameId}")
} }
@Composable @Composable
fun LibraryApp(modifier: Modifier = Modifier) { fun LibraryApp(modifier: Modifier = Modifier) {
val navController = rememberNavController() val navController = rememberNavController()
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
Scaffold( Scaffold(
bottomBar = { bottomBar = {
// Hide bottom bar only when in the Detail view
if (currentRoute != LibraryRoutes.Detail.route) {
androidx.compose.material3.NavigationBar { androidx.compose.material3.NavigationBar {
NavigationBarItem( NavigationBarItem(
icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") }, icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") },
label = { Text("List") }, label = { Text("List") },
selected = navController.currentBackStackEntryAsState().value?.destination?.route == "list", selected = currentRoute == LibraryRoutes.List.route,
onClick = { navController.navigate(DemoRoutes.List.route) } onClick = { navController.navigate(LibraryRoutes.List.route) }
) )
NavigationBarItem( NavigationBarItem(
icon = { Icon(Icons.Default.Add, contentDescription = "Add") }, icon = { Icon(Icons.Default.Add, contentDescription = "Add") },
label = { Text("Add") }, label = { Text("Search") },
selected = navController.currentBackStackEntryAsState().value?.destination?.route == "add", selected = currentRoute == LibraryRoutes.Add.route,
onClick = { navController.navigate(DemoRoutes.Add.route) } onClick = { navController.navigate(LibraryRoutes.Add.route) }
) )
} }
} }
}
) { innerPadding -> ) { innerPadding ->
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = DemoRoutes.List.route, startDestination = LibraryRoutes.List.route,
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding),
contentAlignment = Alignment.Center
) { ) {
composable(DemoRoutes.List.route){ composable(LibraryRoutes.List.route){
// screen GameList(onCardClick = {
navController.navigate("game/$it")
})
} }
composable(DemoRoutes.Add.route) { composable(LibraryRoutes.Add.route) {
// screen SearchList(onCardClick = {
navController.navigate("game/$it")
})
} }
composable( composable(
route = DemoRoutes.Detail.route, route = LibraryRoutes.Detail.route,
arguments = listOf(navArgument("taskId") { arguments = listOf(navArgument("gameId") {
type = NavType.IntType type = NavType.IntType
}) })
) { ) {
// screen backStackEntry ->
DetailView(
navBackStackEntry = backStackEntry,
modifier = Modifier
)
} }
} }
} }
@ -110,23 +109,93 @@ fun LibraryApp(modifier: Modifier = Modifier) {
@Composable @Composable
fun HorizontalCard(modifier: Modifier = Modifier, title: String, details: String) { fun DetailCard(
modifier: Modifier = Modifier,
title: String,
details: String,
note: String,
imageUrl: String) {
Surface( Surface(
modifier = modifier modifier = modifier
.requiredWidth(360.dp) .fillMaxWidth()
.requiredHeight(80.dp) .padding(horizontal = 16.dp, vertical = 4.dp)
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.border(1.dp, Color.LightGray, RoundedCornerShape(12.dp))
) { ) {
Row(modifier = Modifier.fillMaxSize()) { Column(modifier) {
LayoutMediaText(modifier = Modifier.weight(1f), title, details) ShowPicture(modifier = Modifier.height(200.dp), imageUrl)
ShowPicture(modifier = Modifier.weight(0.5f)) 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 @Composable
fun LayoutMediaText(modifier: Modifier = Modifier, title: String, details: String) { 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( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = modifier modifier = modifier
@ -173,7 +242,7 @@ fun LayoutMediaText(modifier: Modifier = Modifier, title: String, details: Strin
} }
@Composable @Composable
fun ShowPicture(modifier: Modifier = Modifier) { fun ShowPicture(modifier: Modifier = Modifier, imageUrl: String) {
Surface( Surface(
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
color = Color(0xfffef7ff), color = Color(0xfffef7ff),
@ -182,9 +251,10 @@ fun ShowPicture(modifier: Modifier = Modifier) {
) { ) {
//Display Image here //Display Image here
AsyncImage( AsyncImage(
model = "https://media.rawg.io/media/games/b7f/b7ffc4c4776e61eca19d36d3c227f89a.jpg", model = imageUrl,
contentDescription = "Translated description of what the image contains", 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) @Preview(widthDp = 360, heightDp = 80)
@Composable @Composable
private fun HorizontalCardPreview() { 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] [versions]
agp = "8.6.0" agp = "8.6.0"
coilCompose = "3.0.4" coilCompose = "3.0.4"
converterKotlinxSerialization = "2.11.0"
kotlin = "1.9.0" kotlin = "1.9.0"
coreKtx = "1.15.0" coreKtx = "1.15.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"
kotlinxSerializationJson = "1.6.3"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3" activityCompose = "1.9.3"
composeBom = "2024.04.01" composeBom = "2024.04.01"
navigationCompose = "2.8.4" navigationCompose = "2.8.4"
roomRuntime = "2.6.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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-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" } 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" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } 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-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }