Skip to content

Commit

Permalink
Update project
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbel committed Dec 22, 2024
1 parent bbf91cf commit 49b403a
Show file tree
Hide file tree
Showing 17 changed files with 506 additions and 64 deletions.
Binary file modified .github/pics/tv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 4 additions & 8 deletions auto/src/main/kotlin/org/michaelbel/template/ui/TabNavigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,20 @@ sealed interface AppNavigation {
@Serializable
data object Main: AppNavigation

//@Parcelize
@Serializable
data class Details(
val id: Int
): AppNavigation//, Parcelable
): AppNavigation
}

sealed interface TabNavigation {

//@Parcelize
@Serializable
data object Home: TabNavigation//, Parcelable
data object Home: TabNavigation

//@Parcelize
@Serializable
data object Settings: TabNavigation//, Parcelable
data object Settings: TabNavigation

//@Parcelize
@Serializable
data object About: TabNavigation//, Parcelable
data object About: TabNavigation
}
4 changes: 4 additions & 0 deletions tv/src/main/kotlin/org/michaelbel/template/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import org.michaelbel.template.ktor.AppService
import org.michaelbel.template.repository.AppRepository
import org.michaelbel.template.room.AppDao
import org.michaelbel.template.room.AppDatabase
import org.michaelbel.template.ui.list.ListViewModel
import org.michaelbel.template.ui.details2.DetailsViewModel2

val appModule = module {
includes(dispatchersKoinModule)
Expand Down Expand Up @@ -80,4 +82,6 @@ val appModule = module {
single<AppRepository> { AppRepository(get(), get(), get()) }
single<AppInteractor> { AppInteractor(get(), get()) }
viewModelOf(::MainViewModel)
viewModelOf(::ListViewModel)
viewModelOf(::DetailsViewModel2)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
package org.michaelbel.template.interactor

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.michaelbel.core.dispatchers.AppDispatchers
import org.michaelbel.template.repository.AppRepository
import org.michaelbel.template.room.AppEntity

class AppInteractor(
private val appDispatchers: AppDispatchers,
private val appRepository: AppRepository
)
) {
fun entitiesFlow(): Flow<List<AppEntity>> {
return appRepository.entitiesFlow()
}

fun entityFlow(id: Int): Flow<AppEntity> {
return appRepository.entityFlow(id)
}

suspend fun loadDataResponse() {
withContext(appDispatchers.io) { appRepository.loadDataResponse() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class AppResponse(
@SerialName("id") val id: Int,
@SerialName("name") val name: String
@SerialName("name") val name: String,
@SerialName("description") val description: String,
@SerialName("picture") val picture: String
)
13 changes: 7 additions & 6 deletions tv/src/main/kotlin/org/michaelbel/template/ktor/AppService.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
package org.michaelbel.template.ktor

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json

class AppService(
private val httpClient: HttpClient
) {

suspend fun getAppResponse(id: Int): AppResponse {
return httpClient.get("route/$id") {
parameter("key", "1234")
}.body()
suspend fun getDataResponse(): List<AppResponse> {
val response: String = httpClient.get(DATA_URL).bodyAsText()
return Json.decodeFromString<List<AppResponse>>(response)
}

companion object {
const val REQUEST_TIMEOUT_MILLIS = 10_000L
const val SOCKET_TIMEOUT_SECONDS = 10_000L
const val HTTP_CACHE_SIZE_BYTES = 1024 * 1024 * 50
const val CONNECT_TIMEOUT_MILLIS = 10_000L

const val DATA_URL = "https://raw.githubusercontent.com/michaelbel/android-template/refs/heads/develop/.github/data/data.json"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
package org.michaelbel.template.repository

import kotlinx.coroutines.flow.Flow
import org.michaelbel.template.datastore.AppPreferences
import org.michaelbel.template.ktor.AppService
import org.michaelbel.template.room.AppDao
import org.michaelbel.template.room.AppEntity

class AppRepository(
private val appPreferences: AppPreferences,
private val appDao: AppDao,
private val appService: AppService
)
) {
fun entitiesFlow(): Flow<List<AppEntity>> {
return appDao.entitiesFlow()
}

fun entityFlow(id: Int): Flow<AppEntity> {
return appDao.entityFlow(id)
}

suspend fun loadDataResponse() {
val appEntities = appService.getDataResponse().map {
AppEntity(
id = it.id,
name = it.name,
description = it.description,
picture = it.picture
)
}
appDao.insertEntities(appEntities)
}
}
6 changes: 5 additions & 1 deletion tv/src/main/kotlin/org/michaelbel/template/room/AppDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.michaelbel.template.room

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow

Expand All @@ -11,6 +12,9 @@ interface AppDao {
@Query("SELECT * FROM entities")
fun entitiesFlow(): Flow<List<AppEntity>>

@Insert
@Query("SELECT * FROM entities WHERE id = :id")
fun entityFlow(id: Int): Flow<AppEntity>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertEntities(entities: List<AppEntity>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ abstract class AppDatabase: RoomDatabase() {
abstract fun appDao(): AppDao

companion object {
const val DATABASE_NAME = "app.db"
private const val DATABASE_NAME = "app.db"
const val DATABASE_VERSION = 1

@Volatile
Expand Down
10 changes: 8 additions & 2 deletions tv/src/main/kotlin/org/michaelbel/template/room/AppEntity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@ import androidx.room.Entity
@Entity(tableName = "entities", primaryKeys = ["id"])
data class AppEntity(
val id: Int,
val name: String
)
val name: String,
val description: String,
val picture: String
) {
companion object {
val Empty = AppEntity(id = 0, name = "", description = "", picture = "")
}
}
158 changes: 115 additions & 43 deletions tv/src/main/kotlin/org/michaelbel/template/ui/MainActivityContent.kt
Original file line number Diff line number Diff line change
@@ -1,72 +1,144 @@
@file:OptIn(ExperimentalMaterial3AdaptiveApi::class)

package org.michaelbel.template.ui

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
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.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.tv.material3.Button
import androidx.tv.material3.Icon
import androidx.tv.material3.Text
import org.koin.androidx.compose.koinViewModel
import org.michaelbel.template.MainViewModel
import org.michaelbel.template.ui.details2.DetailsScreen2
import org.michaelbel.template.ui.details2.empty.DetailsEmptyScreen
import org.michaelbel.template.ui.list.ListScreen

@Composable
fun MainActivityContent(
modifier: Modifier = Modifier,
viewModel: MainViewModel = koinViewModel()
) {
val navController = rememberNavController()
val navHostController = rememberNavController()
var selectedRoute by remember { mutableStateOf<TabNavigation>(TabNavigation.Home) }
val listDetailPaneScaffoldNavigator = rememberListDetailPaneScaffoldNavigator<AppNavigation.Details>()

NavHost(
navController = navController,
startDestination = Navigation.Home.route
) {
composable(Navigation.Home.route) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(
onClick = { navController.navigate(Navigation.Chat.route) }
) {
Text("Navigate to Chat")
NavigationSuiteScaffold(
navigationSuiteItems = {
item(
selected = selectedRoute == TabNavigation.Home,
onClick = { selectedRoute = TabNavigation.Home },
icon = {
Icon(
imageVector = Icons.Outlined.Home,
contentDescription = null
)
}
}
)
item(
selected = selectedRoute == TabNavigation.Settings,
onClick = { selectedRoute = TabNavigation.Settings },
icon = {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = null
)
}
)
item(
selected = selectedRoute == TabNavigation.About,
onClick = { selectedRoute = TabNavigation.About },
icon = {
Icon(
imageVector = Icons.Outlined.Info,
contentDescription = null
)
}
)
}
composable(Navigation.Chat.route) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(
onClick = { navController.navigate(Navigation.Settings.route) }
) {
NavHost(
navController = navHostController,
startDestination = selectedRoute
) {
composable<TabNavigation.Home> {
ListDetailPaneScaffold(
directive = listDetailPaneScaffoldNavigator.scaffoldDirective,
value = listDetailPaneScaffoldNavigator.scaffoldValue,
listPane = {
AnimatedPane(
modifier = Modifier
.navigationBarsPadding()
.fillMaxWidth(0.5F)
) {
ListScreen(
onClick = { listDetailPaneScaffoldNavigator.navigateTo(
ListDetailPaneScaffoldRole.Detail, AppNavigation.Details(it)) }
)
}
},
detailPane = {
AnimatedPane(
modifier = Modifier.fillMaxWidth(0.5F)
) {
when {
listDetailPaneScaffoldNavigator.currentDestination?.content != null -> {
DetailsScreen2(
id = listDetailPaneScaffoldNavigator.currentDestination?.content?.id!!
)
}
else -> {
DetailsEmptyScreen()
}
}
}
},
modifier = Modifier.fillMaxWidth()
)
}
composable<TabNavigation.Settings> {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Navigate to Settings")
Text(
text = "Settings"
)
}
}
}
composable(Navigation.Settings.route) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(
onClick = { navController.navigate(Navigation.Home.route) }
composable<TabNavigation.About> {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Text("Navigate to Home")
Text(
text = "About"
)
}
}
}
}
}

sealed class Navigation(
val route: String
) {
data object Home: Navigation("home")
data object Chat: Navigation("chat")
data object Settings: Navigation("settings")
}
Loading

0 comments on commit 49b403a

Please sign in to comment.