A lot of improvements and restructuring
This commit is contained in:
parent
823054d17d
commit
44130d05a0
|
@ -47,4 +47,5 @@ android {
|
|||
isMinifyEnabled = true
|
||||
}
|
||||
}
|
||||
namespace = "technology.iatlas.spaceup.android"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="technology.iatlas.spaceup.android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:label="SpaceUp-NextUI"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package technology.iatlas.spaceup.android
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.core.view.WindowCompat
|
||||
import moe.tlaster.precompose.lifecycle.PreComposeActivity
|
||||
import moe.tlaster.precompose.lifecycle.setContent
|
||||
|
|
|
@ -12,7 +12,7 @@ allprojects {
|
|||
plugins {
|
||||
kotlin("multiplatform") apply false
|
||||
kotlin("android") apply false
|
||||
id("com.android.application") apply false version "7.3.1"
|
||||
id("com.android.library") apply false version "7.3.1"
|
||||
id("com.android.application") apply false version "7.4.1"
|
||||
id("com.android.library") apply false version "7.4.1"
|
||||
id("org.jetbrains.compose") apply false
|
||||
}
|
|
@ -84,7 +84,6 @@ kotlin {
|
|||
dependencies {
|
||||
api("androidx.appcompat:appcompat:1.6.1")
|
||||
api("androidx.core:core-ktx:1.9.0")
|
||||
implementation("com.github.tony19:logback-android:3.0.0")
|
||||
|
||||
val ktorVersion = "2.2.4"
|
||||
implementation("io.ktor:ktor-client-android:$ktorVersion")
|
||||
|
@ -122,6 +121,7 @@ android {
|
|||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
namespace = "technology.iatlas.spaceup.common"
|
||||
}
|
||||
dependencies {
|
||||
implementation("io.ktor:ktor-client-auth:2.2.4")
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="technology.iatlas.spaceup.common"/>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
|
|
@ -12,10 +12,10 @@ import io.ktor.serialization.kotlinx.json.*
|
|||
|
||||
actual fun httpClient(bearerToken: String): HttpClient {
|
||||
return HttpClient(Android) {
|
||||
install(Logging) {
|
||||
/*install(Logging) {
|
||||
logger = Logger.DEFAULT
|
||||
level = LogLevel.ALL
|
||||
}
|
||||
}*/
|
||||
install(ContentNegotiation) {
|
||||
gson()
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import androidx.compose.material3.IconButton
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
|
@ -47,7 +46,6 @@ import androidx.compose.ui.unit.Density
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.example.compose.AppTheme
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tlaster.precompose.navigation.NavHost
|
||||
import moe.tlaster.precompose.navigation.Navigator
|
||||
|
@ -55,8 +53,11 @@ import moe.tlaster.precompose.navigation.RouteBuilder
|
|||
import moe.tlaster.precompose.navigation.rememberNavigator
|
||||
import moe.tlaster.precompose.navigation.transition.NavTransition
|
||||
import moe.tlaster.precompose.ui.LocalViewModelStoreOwner
|
||||
import moe.tlaster.precompose.ui.viewModel
|
||||
import moe.tlaster.precompose.viewmodel.ViewModelStoreOwner
|
||||
import technology.iatlas.spaceup.common.model.Routes
|
||||
import technology.iatlas.spaceup.common.theme.AppTheme
|
||||
import technology.iatlas.spaceup.common.viewmodel.ServerViewModel
|
||||
import technology.iatlas.spaceup.common.views.Home
|
||||
import technology.iatlas.spaceup.common.views.Login
|
||||
import technology.iatlas.spaceup.common.views.SettingsView
|
||||
|
@ -67,9 +68,13 @@ fun App(useDarkTheme: Boolean = isSystemInDarkTheme()) {
|
|||
val navigator = rememberNavigator()
|
||||
val viewModelStoreOwner = LocalViewModelStoreOwner.current
|
||||
|
||||
val serverViewModel = viewModel(ServerViewModel::class) {
|
||||
ServerViewModel()
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navigator = navigator,
|
||||
initialRoute = Routes.HOME.path
|
||||
initialRoute = if(serverViewModel.token.accessToken.isNotEmpty()) Routes.HOME.path else Routes.LOGIN.path
|
||||
) {
|
||||
scene(route = Routes.LOGIN.path, navigation = navigator, viewModelStoreOwner = viewModelStoreOwner) {
|
||||
Login(it)
|
||||
|
@ -135,48 +140,52 @@ fun Drawer(
|
|||
val drawerList = Routes.values().toList()
|
||||
ModalNavigationDrawer(
|
||||
content = {
|
||||
Surface {
|
||||
Scaffold(
|
||||
//modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
alpha = 1.0f,
|
||||
brush = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color.Blue,
|
||||
Color.Red
|
||||
),
|
||||
startX = 10.0f,
|
||||
endX = 20.0f
|
||||
)
|
||||
),
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
|
||||
title = { Text("NextUI") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
coroutine.launch {
|
||||
drawerState.open()
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Default.Menu, contentDescription = "Menu")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
|
||||
}) {
|
||||
Icon(Icons.Default.Search, contentDescription = "Search")
|
||||
Scaffold(
|
||||
//modifier = Modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
alpha = 1.0f,
|
||||
brush = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color.Gray,
|
||||
Color( 0, 128, 128), // Teal
|
||||
Color.Gray
|
||||
),
|
||||
//startX = 10.0f,
|
||||
//endX = 20.0f
|
||||
)
|
||||
)
|
||||
.padding(0.dp),
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
|
||||
title = { Text("NextUI") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
coroutine.launch {
|
||||
drawerState.open()
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Default.Menu, contentDescription = "Menu")
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
content.invoke(navigation)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
|
||||
}) {
|
||||
Icon(Icons.Default.Search, contentDescription = "Search")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Column(modifier = Modifier
|
||||
//.fillMaxSize()
|
||||
.padding(top = it.calculateTopPadding())
|
||||
) {
|
||||
content.invoke(navigation)
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
drawerState = drawerState,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.example.compose
|
||||
package technology.iatlas.spaceup.common.theme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val md_theme_light_primary = Color(0xFF006A61)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.example.compose
|
||||
package technology.iatlas.spaceup.common.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -75,7 +75,7 @@ private val DarkColors = darkColorScheme(
|
|||
@Composable
|
||||
fun AppTheme(
|
||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable() () -> Unit
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colors = if (!useDarkTheme) {
|
||||
LightColors
|
||||
|
|
|
@ -13,6 +13,16 @@ class ServerViewModel : ViewModel() {
|
|||
// Contains the JWT for authentication
|
||||
var token by mutableStateOf(Token("", "", 0))
|
||||
|
||||
/**
|
||||
* Check if the given JWT is valid or not
|
||||
*
|
||||
* @return is valid JWT
|
||||
*/
|
||||
fun validToken(): Boolean {
|
||||
// TODO me
|
||||
return true
|
||||
}
|
||||
|
||||
fun client(): HttpClient {
|
||||
return httpClient(token.accessToken)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package technology.iatlas.spaceup.common.views
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -35,7 +40,8 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.ExperimentalUnitApi
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
|
@ -47,8 +53,8 @@ import moe.tlaster.precompose.ui.viewModel
|
|||
import technology.iatlas.spaceup.common.components.Alert
|
||||
import technology.iatlas.spaceup.common.components.FullscreenCircularLoader
|
||||
import technology.iatlas.spaceup.common.model.Domain
|
||||
import technology.iatlas.spaceup.common.model.Routes
|
||||
import technology.iatlas.spaceup.common.openInBrowser
|
||||
import technology.iatlas.spaceup.common.util.httpClient
|
||||
import technology.iatlas.spaceup.common.viewmodel.ServerViewModel
|
||||
|
||||
@Composable
|
||||
|
@ -67,10 +73,7 @@ fun Home(navigator: Navigator) {
|
|||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Row(
|
||||
//verticalAlignment = Alignment.CenterVertically,
|
||||
//horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Row {
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
|
@ -116,7 +119,6 @@ fun Home(navigator: Navigator) {
|
|||
}
|
||||
)
|
||||
}
|
||||
Button(onClick = {}) { Text("Test") }
|
||||
|
||||
// Domain content
|
||||
MessageList(domains)
|
||||
|
@ -129,116 +131,107 @@ fun Home(navigator: Navigator) {
|
|||
if (isLoading && !cached) FullscreenCircularLoader(isLoading)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// TODO validate JWT token here
|
||||
if (serverViewModel.token.accessToken.isEmpty()) {
|
||||
// TODO only for testing commented out
|
||||
//navigator.navigate(Routes.LOGOUT.path)
|
||||
} else {
|
||||
try {
|
||||
// TODO move this to domainViewModel
|
||||
// "Authorization": 'Bearer $jwt'
|
||||
val response = serverViewModel.client()
|
||||
.get("${serverViewModel.serverUrl}/api/domain/list?cached=true") {
|
||||
headers {
|
||||
append("Authorization", "Bearer ${serverViewModel.token.accessToken}")
|
||||
}
|
||||
try {
|
||||
// TODO move this to domainViewModel
|
||||
// "Authorization": 'Bearer $jwt'
|
||||
val response = serverViewModel.client()
|
||||
.get("${serverViewModel.serverUrl}/api/domain/list?cached=true") {
|
||||
headers {
|
||||
append("Authorization", "Bearer ${serverViewModel.token.accessToken}")
|
||||
}
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
response.body<List<Domain>>().forEach {
|
||||
if (!domains.contains(it)) {
|
||||
domains.add(it)
|
||||
}
|
||||
}
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
response.body<List<Domain>>().forEach {
|
||||
if (!domains.contains(it)) {
|
||||
domains.add(it)
|
||||
}
|
||||
} else {
|
||||
// Show error
|
||||
errorMsg.value = response.bodyAsText()
|
||||
openDialog.value = true
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
if (ex.message != null) {
|
||||
errorMsg.value = ex.message!!
|
||||
}
|
||||
} else {
|
||||
// Show error
|
||||
errorMsg.value = response.bodyAsText()
|
||||
openDialog.value = true
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
if (ex.message != null) {
|
||||
errorMsg.value = ex.message!!
|
||||
}
|
||||
openDialog.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnitApi::class, ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun MessageList(domains: List<Domain>) {
|
||||
var openInBrowser by remember { mutableStateOf(false) }
|
||||
val expanded = mutableStateOf(false)
|
||||
val selectedDomain = mutableStateOf("")
|
||||
|
||||
var selectedDomain by remember { mutableStateOf("") }
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
items(domains.size) { index ->
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onDoubleTap = {
|
||||
selectedDomain = "https://${domains[index].url}"
|
||||
}
|
||||
)
|
||||
}
|
||||
.height(48.dp)
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
AnimatedVisibility(
|
||||
domains[index].url.isNotEmpty(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceAround
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.padding(4.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
) {
|
||||
Text(text = domains[index].url)
|
||||
IconButton(
|
||||
onClick = {
|
||||
selectedDomain = "https://${domains[index].url}"
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
"More"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
items(domains.size) {
|
||||
DropdownMenu(
|
||||
expanded = selectedDomain.isNotEmpty(),
|
||||
onDismissRequest = { selectedDomain = "" },
|
||||
) {
|
||||
if (openInBrowser) {
|
||||
openInBrowser(selectedDomain)
|
||||
openInBrowser = false
|
||||
expanded.value = false
|
||||
}
|
||||
DropdownMenuItem(
|
||||
text = { Text("Open") },
|
||||
onClick = {
|
||||
openInBrowser = true
|
||||
})
|
||||
Divider()
|
||||
DropdownMenuItem(
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
text = {
|
||||
Text(
|
||||
"Delete",
|
||||
style = TextStyle(color = MaterialTheme.colorScheme.error)
|
||||
text = domains[index].url,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
// Delete Domain with Alert warning
|
||||
})
|
||||
IconButton(
|
||||
onClick = {
|
||||
expanded.value = !expanded.value
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
"More"
|
||||
)
|
||||
DropdownMenu(
|
||||
expanded = expanded.value,
|
||||
onDismissRequest = { expanded.value = false },
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Open") },
|
||||
onClick = {
|
||||
selectedDomain.value = "https://${domains[index].url}"
|
||||
})
|
||||
Divider()
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
"Delete",
|
||||
style = TextStyle(color = MaterialTheme.colorScheme.error)
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
// Delete Domain with Alert warning
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(selectedDomain.value.isNotEmpty()) {
|
||||
openInBrowser(selectedDomain.value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ fun Login(navigator: Navigator) {
|
|||
Column(
|
||||
// modifier = Modifier.fillMaxSize(), // To make it center center
|
||||
modifier = Modifier
|
||||
.padding(top = 18.dp)
|
||||
.padding(top = 48.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
|
|
|
@ -6,12 +6,12 @@ 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.material.Button
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
|
@ -34,7 +34,6 @@ fun SettingsView(navigator: Navigator) {
|
|||
)
|
||||
|
||||
Column {
|
||||
Button({navigator.goBack()}) { Text("Go back") }
|
||||
Settings(settingsList = settingsList)
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +59,7 @@ fun SettingItem(setting: SettingItem) {
|
|||
Icon(imageVector = setting.icon, contentDescription = null)
|
||||
Column(modifier = Modifier.weight(1f).padding(start = 16.dp)) {
|
||||
Text(text = setting.title)
|
||||
Text(text = setting.description, style = MaterialTheme.typography.caption)
|
||||
Text(text = setting.description, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Checkbox(checked = setting.value, onCheckedChange = setting.action)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue