A lot of improvements and restructuring

This commit is contained in:
Thraax Session 2023-04-05 21:05:50 +02:00
parent 823054d17d
commit 44130d05a0
15 changed files with 167 additions and 154 deletions

View File

@ -47,4 +47,5 @@ android {
isMinifyEnabled = true
}
}
namespace = "technology.iatlas.spaceup.android"
}

View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -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")

View File

@ -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"/>

View File

@ -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()
}

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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)
}

0
gradlew vendored Normal file → Executable file
View File