0.0.1
This commit is contained in:
parent
7d6dec2d4f
commit
133e06db1e
@ -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)
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
8
app/src/main/java/at/xaxa/gamelibraryapp/data/Game.kt
Normal file
8
app/src/main/java/at/xaxa/gamelibraryapp/data/Game.kt
Normal 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
|
||||||
|
)
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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>>
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
@ -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
|
||||||
|
)
|
@ -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)
|
||||||
|
}
|
@ -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)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
|
)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
package at.xaxa.gamelibraryapp.ui.GameList
|
||||||
|
|
@ -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 = {
|
||||||
androidx.compose.material3.NavigationBar {
|
// Hide bottom bar only when in the Detail view
|
||||||
NavigationBarItem(
|
if (currentRoute != LibraryRoutes.Detail.route) {
|
||||||
icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") },
|
androidx.compose.material3.NavigationBar {
|
||||||
label = { Text("List") },
|
NavigationBarItem(
|
||||||
selected = navController.currentBackStackEntryAsState().value?.destination?.route == "list",
|
icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List") },
|
||||||
onClick = { navController.navigate(DemoRoutes.List.route) }
|
label = { Text("List") },
|
||||||
)
|
selected = currentRoute == LibraryRoutes.List.route,
|
||||||
NavigationBarItem(
|
onClick = { navController.navigate(LibraryRoutes.List.route) }
|
||||||
icon = { Icon(Icons.Default.Add, contentDescription = "Add") },
|
)
|
||||||
label = { Text("Add") },
|
NavigationBarItem(
|
||||||
selected = navController.currentBackStackEntryAsState().value?.destination?.route == "add",
|
icon = { Icon(Icons.Default.Add, contentDescription = "Add") },
|
||||||
onClick = { navController.navigate(DemoRoutes.Add.route) }
|
label = { Text("Search") },
|
||||||
)
|
selected = currentRoute == LibraryRoutes.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")
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
package at.xaxa.gamelibraryapp.ui.Search
|
||||||
|
|
@ -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" }
|
||||||
|
Loading…
Reference in New Issue
Block a user