diff --git a/README.md b/README.md
index 914aa22..add664b 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,8 @@ The app is fully open source, lightweight, and can run on small devices like Ras
You can either build the app from source or download the apk _(will be available soon)_
+---
+
## License
This work is marked CC0 1.0
\ No newline at end of file
diff --git a/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt b/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt
index 7b4141f..5f9758a 100644
--- a/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt
+++ b/app/src/main/java/de/miaurizius/shap_planner/activities/MainActivity.kt
@@ -46,6 +46,7 @@ import de.miaurizius.shap_planner.UserPreferences
import de.miaurizius.shap_planner.entities.Account
import de.miaurizius.shap_planner.network.SessionState
import de.miaurizius.shap_planner.room.AppDatabase
+import de.miaurizius.shap_planner.ui.AppContent
import de.miaurizius.shap_planner.ui.theme.ShapPlannerTheme
import de.miaurizius.shap_planner.viewmodels.LoginViewModel
import de.miaurizius.shap_planner.viewmodels.MainViewModel
@@ -54,16 +55,12 @@ import java.util.UUID
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-// enableEdgeToEdge()
val prefs = UserPreferences(this)
val loginViewModel = LoginViewModel(prefs, applicationContext)
-
val database = AppDatabase.getDatabase(applicationContext)
val dao = database.accountDao()
-
val tokenStorage = TokenStorage(applicationContext)
-
val mainViewModel = MainViewModel(dao, tokenStorage)
setContent {
@@ -71,215 +68,30 @@ class MainActivity : ComponentActivity() {
val isLoggedIn by loginViewModel.isLoggedIn.collectAsState()
val accountList by mainViewModel.accounts.collectAsState()
val selectedAccount = mainViewModel.selectedAccount
- var showLoginForNewAccount by remember { mutableStateOf(false) }
+ val showLoginForNewAccount = remember { mutableStateOf(false) }
- when {
-
- showLoginForNewAccount -> {
- LoginScreen(
- onLogin = { serverUrl, username, password ->
- loginViewModel.login(serverUrl, username, password, mainViewModel)
- showLoginForNewAccount = false
- },
- onBack = {
- showLoginForNewAccount = false
- }
- )
- }
-
- accountList.isEmpty() -> {
- LoginScreen(
- onLogin = { serverUrl, username, password ->
- loginViewModel.login(serverUrl, username, password, mainViewModel)
- }
- )
- }
-
- selectedAccount != null -> {
- DashboardScreen(
- account = selectedAccount,
- onBack = { mainViewModel.logoutFromAccount() },
- onDelete = { mainViewModel.deleteAccount(selectedAccount) },
- sessionState = mainViewModel.sessionState,
- onValidate = { mainViewModel.validateSession(selectedAccount) },
- onSessionInvalid = { mainViewModel.logoutFromAccount() }
- )
- }
-
- else -> {
- AccountSelectionScreen(
- accounts = accountList,
- onAccountClick = { account ->
- mainViewModel.selectAccount(account)
- },
- onAddAccountClick = {
- showLoginForNewAccount = true
- }
- )
- }
+ BackHandler(enabled = showLoginForNewAccount.value && accountList.isNotEmpty()) {
+ showLoginForNewAccount.value = false
}
+
+ AppContent(
+ isLoggedIn = isLoggedIn,
+ accountList = accountList,
+ selectedAccount = selectedAccount,
+ showLoginForNewAccount = showLoginForNewAccount.value,
+ onLogin = { server, user, pass ->
+ loginViewModel.login(server, user, pass, mainViewModel)
+ showLoginForNewAccount.value = false
+ },
+ onSelectAccount = { mainViewModel.selectAccount(it) },
+ onLogoutAccount = { mainViewModel.logoutFromAccount() },
+ onAddAccountClick = { showLoginForNewAccount.value = true },
+ onDeleteAccount = { mainViewModel.deleteAccount(selectedAccount!!) },
+ sessionState = mainViewModel.sessionState,
+ onValidateSession = { mainViewModel.validateSession(selectedAccount!!) },
+ onSessionInvalid = { mainViewModel.logoutFromAccount() }
+ )
}
}
}
-}
-
-@Composable
-fun AccountSelectionScreen(accounts: List, onAccountClick: (Account) -> Unit, onAddAccountClick: () -> Unit) {
- LazyColumn(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp)
- .statusBarsPadding()
- .navigationBarsPadding(),
- verticalArrangement = Arrangement.spacedBy(12.dp)
- ) {
- item {
- Text("Wähle einen Account", style = MaterialTheme.typography.headlineSmall)
- }
-
- items(accounts) { account ->
- Card(modifier = Modifier.fillMaxWidth().clickable{ onAccountClick(account) }) {
- Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
- Box(modifier = Modifier.size(40.dp).background(Color.Gray, shape = CircleShape))
- Spacer(modifier = Modifier.width(16.dp))
- Column {
- Text(text = account.name, fontWeight = FontWeight.Bold)
- Text(text = account.wgName, style = MaterialTheme.typography.bodyMedium)
- }
- }
- }
- }
- item {
- Spacer(modifier = Modifier.height(8.dp))
- Button(
- onClick = onAddAccountClick,
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Anderen Account hinzufügen")
- }
- }
- }
-}
-
-@Composable
-fun LoginScreen(onLogin: (String, String, String) -> Unit, onBack: (() -> Unit)? = null) {
-
- if (onBack != null) {
- BackHandler {
- onBack()
- }
- }
-
- var serverUrl by remember { mutableStateOf("") }
- var username by remember { mutableStateOf("") }
- var password by remember { mutableStateOf("") }
-
- Column(modifier = Modifier.padding(16.dp).statusBarsPadding().navigationBarsPadding()) {
- Text("Bitte anmelden")
- Spacer(modifier = Modifier.height(8.dp))
-
- //Home-Server
- TextField(
- value = serverUrl,
- onValueChange = { serverUrl = it },
- label = { Text("Server-URL") }
- )
- Spacer(modifier = Modifier.height(8.dp))
-
- //Username
- TextField(
- value = username,
- onValueChange = { username = it },
- label = { Text("Nutzername") }
- )
- Spacer(modifier = Modifier.height(8.dp))
-
- //Password
- TextField(
- value = password,
- onValueChange = { password = it },
- label = { Text("Passwort") }
- )
- Spacer(modifier = Modifier.height(8.dp))
-
- Button(onClick = { if(serverUrl.isNotEmpty() && username.isNotEmpty() && password.isNotEmpty()) onLogin(
- serverUrl,
- username,
- password
- ) }) {
- Text("Login")
- }
- }
-}
-
-@Composable
-fun DashboardScreen(
- account: Account,
- onBack: () -> Unit,
- onDelete: () -> Unit,
- sessionState: SessionState,
- onValidate: () -> Unit,
- onSessionInvalid: () -> Unit) {
-
- LaunchedEffect(Unit) { onValidate() }
-
- when (sessionState) {
- SessionState.Loading -> {
- Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- CircularProgressIndicator()
- }
- }
- SessionState.Valid -> {
- BackHandler {
- onBack()
- }
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp)
- .statusBarsPadding()
- .navigationBarsPadding()
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Column {
- Text(text = "Hallo, ${account.name}!", style = MaterialTheme.typography.headlineMedium)
- Text(text = "WG: ${account.wgName}", style = MaterialTheme.typography.bodyLarge, color = Color.Gray)
- }
- Button(onClick = onBack) {
- Text("Wechseln")
- }
- }
-
- Spacer(modifier = Modifier.height(5.dp))
-
- Button(onClick = onDelete) {
- Text("Löschen")
- }
-
- Spacer(modifier = Modifier.height(10.dp))
-
- Box(
- modifier = Modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.surfaceVariant, shape = MaterialTheme.shapes.medium),
- contentAlignment = Alignment.Center
- ) {
- Text("Hier kommen bald deine WG-Kosten hin 🚀")
- }
- }
- }
- SessionState.Invalid -> {
- LaunchedEffect(Unit) {
- onSessionInvalid()
- }
- }
- is SessionState.Error -> {
- Text("Server error")
- }
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/de/miaurizius/shap_planner/network/APIService.kt b/app/src/main/java/de/miaurizius/shap_planner/network/APIService.kt
index f292955..fdf9ad6 100644
--- a/app/src/main/java/de/miaurizius/shap_planner/network/APIService.kt
+++ b/app/src/main/java/de/miaurizius/shap_planner/network/APIService.kt
@@ -11,7 +11,7 @@ data class LoginRequest(val username: String, val password: String)
data class LoginUser(val id: String, val username: String, val role: String, val avatarUrl: String?)
data class LoginResponse(val access_token: String, val refresh_token: String, val user: LoginUser, val wgName: String)
-data class RefreshRequest(val refreshToken: String)
+data class RefreshRequest(val refresh_token: String)
data class RefreshResponse(val accessToken: String, val refreshToken: String)
interface APIService {
diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt
new file mode 100644
index 0000000..686ef5c
--- /dev/null
+++ b/app/src/main/java/de/miaurizius/shap_planner/ui/AppContent.kt
@@ -0,0 +1,42 @@
+package de.miaurizius.shap_planner.ui
+
+import androidx.compose.runtime.Composable
+import de.miaurizius.shap_planner.entities.Account
+import de.miaurizius.shap_planner.network.SessionState
+import de.miaurizius.shap_planner.ui.screens.AccountSelectionScreen
+import de.miaurizius.shap_planner.ui.screens.DashboardScreen
+import de.miaurizius.shap_planner.ui.screens.LoginScreen
+
+@Composable
+fun AppContent(
+ isLoggedIn: Boolean,
+ accountList: List,
+ selectedAccount: Account?,
+ showLoginForNewAccount: Boolean,
+ onLogin: (String, String, String) -> Unit,
+ onSelectAccount: (Account) -> Unit,
+ onLogoutAccount: () -> Unit,
+ onAddAccountClick: () -> Unit,
+ onDeleteAccount: () -> Unit,
+ sessionState: SessionState,
+ onValidateSession: () -> Unit,
+ onSessionInvalid: () -> Unit
+) {
+ when {
+ showLoginForNewAccount -> LoginScreen(onLogin)
+ accountList.isEmpty() -> LoginScreen(onLogin)
+ selectedAccount != null -> DashboardScreen(
+ account = selectedAccount,
+ onBack = onLogoutAccount,
+ onDelete = onDeleteAccount,
+ sessionState = sessionState,
+ onValidate = onValidateSession,
+ onSessionInvalid = onSessionInvalid
+ )
+ else -> AccountSelectionScreen(
+ accounts = accountList,
+ onAccountClick = onSelectAccount,
+ onAddAccountClick = onAddAccountClick
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt
new file mode 100644
index 0000000..0d35dda
--- /dev/null
+++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/AccountSelectionScreen.kt
@@ -0,0 +1,69 @@
+package de.miaurizius.shap_planner.ui.screens
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import de.miaurizius.shap_planner.entities.Account
+
+@Composable
+fun AccountSelectionScreen(accounts: List, onAccountClick: (Account) -> Unit, onAddAccountClick: () -> Unit) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ .statusBarsPadding()
+ .navigationBarsPadding(),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ item {
+ Text("Wähle einen Account", style = MaterialTheme.typography.headlineSmall)
+ }
+
+ items(accounts) { account ->
+ Card(modifier = Modifier.fillMaxWidth().clickable{ onAccountClick(account) }) {
+ Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
+ Box(modifier = Modifier.size(40.dp).background(Color.Gray, shape = CircleShape))
+ Spacer(modifier = Modifier.width(16.dp))
+ Column {
+ Text(text = account.name, fontWeight = FontWeight.Bold)
+ Text(text = account.wgName, style = MaterialTheme.typography.bodyMedium)
+ }
+ }
+ }
+ }
+ item {
+ Spacer(modifier = Modifier.height(8.dp))
+ Button(
+ onClick = onAddAccountClick,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("Anderen Account hinzufügen")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt
new file mode 100644
index 0000000..93f4058
--- /dev/null
+++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/DashboardScreen.kt
@@ -0,0 +1,111 @@
+package de.miaurizius.shap_planner.ui.screens
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material3.Button
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import de.miaurizius.shap_planner.entities.Account
+import de.miaurizius.shap_planner.network.SessionState
+
+@Composable
+fun DashboardScreen(
+ account: Account,
+ onBack: () -> Unit,
+ onDelete: () -> Unit,
+ sessionState: SessionState,
+ onValidate: () -> Unit,
+ onSessionInvalid: () -> Unit) {
+
+ LaunchedEffect(Unit) { onValidate() }
+
+ when (sessionState) {
+ SessionState.Loading -> {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ CircularProgressIndicator()
+ }
+ }
+
+ SessionState.Valid -> {
+ BackHandler {
+ onBack()
+ }
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ .statusBarsPadding()
+ .navigationBarsPadding()
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+ Text(
+ text = "Hallo, ${account.name}!",
+ style = MaterialTheme.typography.headlineMedium
+ )
+ Text(
+ text = "WG: ${account.wgName}",
+ style = MaterialTheme.typography.bodyLarge,
+ color = Color.Gray
+ )
+ }
+ Button(onClick = onBack) {
+ Text("Wechseln")
+ }
+ }
+
+ Spacer(modifier = Modifier.height(5.dp))
+
+ Button(onClick = onDelete) {
+ Text("Löschen")
+ }
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ MaterialTheme.colorScheme.surfaceVariant,
+ shape = MaterialTheme.shapes.medium
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text("Hier kommen bald deine WG-Kosten hin 🚀")
+ }
+ }
+ }
+
+ SessionState.Invalid -> {
+ LaunchedEffect(Unit) {
+ onSessionInvalid()
+ }
+ }
+
+ is SessionState.Error -> {
+ Text("Server error")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt
new file mode 100644
index 0000000..67b68f4
--- /dev/null
+++ b/app/src/main/java/de/miaurizius/shap_planner/ui/screens/LoginScreen.kt
@@ -0,0 +1,70 @@
+package de.miaurizius.shap_planner.ui.screens
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+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
+
+@Composable
+fun LoginScreen(onLogin: (String, String, String) -> Unit, onBack: (() -> Unit)? = null) {
+
+ if (onBack != null) {
+ BackHandler {
+ onBack()
+ }
+ }
+
+ var serverUrl by remember { mutableStateOf("") }
+ var username by remember { mutableStateOf("") }
+ var password by remember { mutableStateOf("") }
+
+ Column(modifier = Modifier.padding(16.dp).statusBarsPadding().navigationBarsPadding()) {
+ Text("Bitte anmelden")
+ Spacer(modifier = Modifier.height(8.dp))
+
+ //Home-Server
+ TextField(
+ value = serverUrl,
+ onValueChange = { serverUrl = it },
+ label = { Text("Server-URL") }
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+
+ //Username
+ TextField(
+ value = username,
+ onValueChange = { username = it },
+ label = { Text("Nutzername") }
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+
+ //Password
+ TextField(
+ value = password,
+ onValueChange = { password = it },
+ label = { Text("Passwort") }
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Button(onClick = { if(serverUrl.isNotEmpty() && username.isNotEmpty() && password.isNotEmpty()) onLogin(
+ serverUrl,
+ username,
+ password
+ ) }) {
+ Text("Login")
+ }
+ }
+}
\ No newline at end of file