project creation and currency db

This commit is contained in:
soraefir
2026-06-14 21:34:58 +02:00
parent a9a303478c
commit d617c5c7be
5 changed files with 142 additions and 62 deletions
@@ -430,6 +430,7 @@ fun BillsListScreen(
} }
}, },
drawerContent = { drawerContent = {
val selectedProject = viewModel.projects.find { it.id == viewModel.selectedProjectId }
Drawer( Drawer(
projects = viewModel.projects, projects = viewModel.projects,
members = viewModel.members, members = viewModel.members,
@@ -437,6 +438,7 @@ fun BillsListScreen(
selectedProjectId = viewModel.selectedProjectId, selectedProjectId = viewModel.selectedProjectId,
selectedMemberId = viewModel.selectedMemberId, selectedMemberId = viewModel.selectedMemberId,
lastSyncText = viewModel.lastSyncText, lastSyncText = viewModel.lastSyncText,
mainCurrency = selectedProject?.currencyName,
showArchived = showArchived, showArchived = showArchived,
onProjectClick = { onProjectClick = {
viewModel.selectedMemberId = null viewModel.selectedMemberId = null
@@ -21,6 +21,7 @@ import net.helcel.cowspent.model.*
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
import net.helcel.cowspent.theme.ThemeUtils import net.helcel.cowspent.theme.ThemeUtils
import net.helcel.cowspent.util.* import net.helcel.cowspent.util.*
import java.util.Locale
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.content.edit import androidx.core.content.edit
import net.helcel.cowspent.model.ProjectType import net.helcel.cowspent.model.ProjectType
@@ -60,7 +61,8 @@ class NewProjectActivity : AppCompatActivity() {
chooseFromNextcloud() chooseFromNextcloud()
}, },
onOkPressed = { onPressOk() }, onOkPressed = { onPressOk() },
onBack = { finish() } onBack = { finish() },
onFieldsChanged = { updateAuthStatus() }
) )
} }
} }
@@ -85,6 +87,8 @@ class NewProjectActivity : AppCompatActivity() {
viewModel.projectUrl = defaultNcUrl viewModel.projectUrl = defaultNcUrl
} }
} }
updateAuthStatus()
val defaultProjectId = intent.getStringExtra(PARAM_DEFAULT_PROJECT_ID) val defaultProjectId = intent.getStringExtra(PARAM_DEFAULT_PROJECT_ID)
if (defaultProjectId != null) { if (defaultProjectId != null) {
@@ -139,6 +143,17 @@ class NewProjectActivity : AppCompatActivity() {
} }
} }
private fun updateAuthStatus() {
val url = getFormattedUrl()
val fakeProj = DBProject(
0, "", "", "", url,
"", 0L, viewModel.projectType, 0L,
null, false, DBProject.ACCESS_LEVEL_UNKNOWN,
""
)
viewModel.isAuthenticatedAccount = db.cowspentServerSyncHelper.canCreateAuthenticatedProject(fakeProj)
}
private fun onPressOk() { private fun onPressOk() {
val type = viewModel.projectType val type = viewModel.projectType
val todoCreate = viewModel.whatTodoIsCreate val todoCreate = viewModel.whatTodoIsCreate
@@ -186,7 +201,16 @@ class NewProjectActivity : AppCompatActivity() {
} }
private fun createProject() { private fun createProject() {
val isCospendScheme = isCospendSchemeLink(getFormattedUrl()) val url = getFormattedUrl()
val isCospendScheme = isCospendSchemeLink(url)
if (viewModel.whatTodoIsCreate && viewModel.projectId.isEmpty() && viewModel.projectName.isNotEmpty()) {
viewModel.projectId = viewModel.projectName.lowercase(Locale.ROOT)
.replace("[^a-z0-9]".toRegex(), "-")
.replace("-+".toRegex(), "-")
.trim('-')
}
val rid = viewModel.projectId val rid = viewModel.projectId
if (!isCospendScheme && (rid == "" || rid.contains(",") || rid.contains("/"))) { if (!isCospendScheme && (rid == "" || rid.contains(",") || rid.contains("/"))) {
showToast(getString(R.string.error_invalid_project_remote_id), Toast.LENGTH_LONG) showToast(getString(R.string.error_invalid_project_remote_id), Toast.LENGTH_LONG)
@@ -194,7 +218,7 @@ class NewProjectActivity : AppCompatActivity() {
} }
if (viewModel.projectType != ProjectType.LOCAL && !isCospendScheme) { if (viewModel.projectType != ProjectType.LOCAL && !isCospendScheme) {
if (!isValidUrl(getFormattedUrl())) { if (!isValidUrl(url)) {
showToast("Invalid URL", Toast.LENGTH_SHORT) showToast("Invalid URL", Toast.LENGTH_SHORT)
return return
} }
@@ -217,7 +241,16 @@ class NewProjectActivity : AppCompatActivity() {
showToast("Invalid project name", Toast.LENGTH_SHORT) showToast("Invalid project name", Toast.LENGTH_SHORT)
return return
} }
if (!SupportUtil.isValidEmail(viewModel.projectEmail)) {
val fakeProj = DBProject(
0, rid, "", viewModel.projectName, url,
viewModel.projectEmail, 0L, viewModel.projectType, 0L,
null, false, DBProject.ACCESS_LEVEL_UNKNOWN,
""
)
val isAuthenticated = db.cowspentServerSyncHelper.canCreateAuthenticatedProject(fakeProj)
if (!isAuthenticated && !SupportUtil.isValidEmail(viewModel.projectEmail)) {
showToast("Invalid email", Toast.LENGTH_SHORT) showToast("Invalid email", Toast.LENGTH_SHORT)
return return
} }
@@ -226,7 +259,7 @@ class NewProjectActivity : AppCompatActivity() {
if (!db.cowspentServerSyncHelper.createRemoteProject( if (!db.cowspentServerSyncHelper.createRemoteProject(
viewModel.projectId, viewModel.projectName, viewModel.projectId, viewModel.projectName,
viewModel.projectEmail, viewModel.projectPassword, getFormattedUrl(), viewModel.projectType, createRemoteCallBack viewModel.projectEmail, viewModel.projectPassword, url, viewModel.projectType, createRemoteCallBack
) )
) { ) {
viewModel.isCreatingRemoteProject = false viewModel.isCreatingRemoteProject = false
@@ -32,8 +32,13 @@ fun NewProjectScreen(
onImportFile: () -> Unit, onImportFile: () -> Unit,
onChooseFromNextcloud: () -> Unit, onChooseFromNextcloud: () -> Unit,
onOkPressed: () -> Unit, onOkPressed: () -> Unit,
onBack: () -> Unit onBack: () -> Unit,
onFieldsChanged: () -> Unit
) { ) {
LaunchedEffect(viewModel.projectUrl, viewModel.projectType) {
onFieldsChanged()
}
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -162,16 +167,18 @@ fun NewProjectScreen(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
} }
OutlinedTextField( if (!viewModel.whatTodoIsCreate || !viewModel.isAuthenticatedAccount || viewModel.projectType != ProjectType.COSPEND) {
value = viewModel.projectId, OutlinedTextField(
onValueChange = { viewModel.projectId = it }, value = viewModel.projectId,
label = { Text(stringResource(R.string.setting_project_id)) }, onValueChange = { viewModel.projectId = it },
modifier = Modifier.fillMaxWidth(), label = { Text(stringResource(R.string.setting_project_id)) },
leadingIcon = { Icon(Icons.AutoMirrored.Filled.LibraryBooks, contentDescription = null) } modifier = Modifier.fillMaxWidth(),
) leadingIcon = { Icon(Icons.AutoMirrored.Filled.LibraryBooks, contentDescription = null) }
)
Spacer(modifier = Modifier.height(8.dp))
}
if (viewModel.projectType != ProjectType.LOCAL && (!viewModel.whatTodoIsCreate || viewModel.projectType == ProjectType.IHATEMONEY)) { if (viewModel.projectType != ProjectType.LOCAL && (!viewModel.whatTodoIsCreate || viewModel.projectType == ProjectType.IHATEMONEY)) {
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = viewModel.projectPassword, value = viewModel.projectPassword,
onValueChange = { viewModel.projectPassword = it }, onValueChange = { viewModel.projectPassword = it },
@@ -179,10 +186,10 @@ fun NewProjectScreen(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) } leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) }
) )
Spacer(modifier = Modifier.height(8.dp))
} }
if (viewModel.whatTodoIsCreate && viewModel.projectType != ProjectType.LOCAL) { if (viewModel.whatTodoIsCreate && viewModel.projectType != ProjectType.LOCAL) {
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = viewModel.projectName, value = viewModel.projectName,
onValueChange = { viewModel.projectName = it }, onValueChange = { viewModel.projectName = it },
@@ -190,14 +197,17 @@ fun NewProjectScreen(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) } leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
) )
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( if (!viewModel.isAuthenticatedAccount || viewModel.projectType == ProjectType.IHATEMONEY) {
value = viewModel.projectEmail, Spacer(modifier = Modifier.height(8.dp))
onValueChange = { viewModel.projectEmail = it }, OutlinedTextField(
label = { Text(stringResource(R.string.setting_new_project_email)) }, value = viewModel.projectEmail,
modifier = Modifier.fillMaxWidth(), onValueChange = { viewModel.projectEmail = it },
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) } label = { Text(stringResource(R.string.setting_new_project_email)) },
) modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) }
)
}
} }
} }
} }
@@ -334,6 +344,7 @@ fun NewProjectScreenPreview() {
onImportFile = {}, onImportFile = {},
onChooseFromNextcloud = {}, onChooseFromNextcloud = {},
onOkPressed = {}, onOkPressed = {},
onBack = {} onBack = {},
onFieldsChanged = {}
) )
} }
@@ -30,6 +30,8 @@ class NewProjectViewModel : ViewModel() {
var projectName by mutableStateOf("") var projectName by mutableStateOf("")
var projectEmail by mutableStateOf("") var projectEmail by mutableStateOf("")
var isAuthenticatedAccount by mutableStateOf(false)
var showAuthWarningDialog by mutableStateOf(false) var showAuthWarningDialog by mutableStateOf(false)
var showNextcloudProjectDialog by mutableStateOf(false) var showNextcloudProjectDialog by mutableStateOf(false)
var nextcloudProjects by mutableStateOf<List<DBAccountProject>>(emptyList()) var nextcloudProjects by mutableStateOf<List<DBAccountProject>>(emptyList())
@@ -39,12 +41,23 @@ class NewProjectViewModel : ViewModel() {
fun isFormValid(): Boolean { fun isFormValid(): Boolean {
if (whatTodoIsCreate) { if (whatTodoIsCreate) {
if (projectId.isEmpty()) return false if (projectType == ProjectType.LOCAL) {
if (projectType != ProjectType.LOCAL) { return projectId.isNotEmpty()
} else {
if (projectUrl.isEmpty()) return false if (projectUrl.isEmpty()) return false
if (projectName.isEmpty()) return false if (projectName.isEmpty()) return false
if (projectEmail.isEmpty()) return false
if (projectType == ProjectType.IHATEMONEY && projectPassword.isEmpty()) return false if (projectType == ProjectType.COSPEND && isAuthenticatedAccount) {
return true
}
if (projectId.isEmpty()) return false
if (projectType == ProjectType.IHATEMONEY) {
if (projectEmail.isEmpty()) return false
if (projectPassword.isEmpty()) return false
} else {
if (projectEmail.isEmpty()) return false
}
} }
} else { } else {
// Join // Join
@@ -320,48 +320,69 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
} }
} }
val currenciesToDelete = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_DELETED) if (project.type == ProjectType.COSPEND) {
for (cToDel in currenciesToDelete) { val currenciesToDelete = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_DELETED)
try { for (cToDel in currenciesToDelete) {
val deleteRemoteCurrencyResponse = client!!.deleteRemoteCurrency(project, cToDel.remoteId) try {
if (deleteRemoteCurrencyResponse.stringContent == "OK") { val deleteRemoteCurrencyResponse = client!!.deleteRemoteCurrency(project, cToDel.remoteId)
Log.d(TAG, "successfully deleted currency on remote project : delete it locally") if (deleteRemoteCurrencyResponse.stringContent == "OK") {
dbHelper.deleteCurrency(cToDel.id) Log.d(TAG, "successfully deleted currency on remote project : delete it locally")
} dbHelper.deleteCurrency(cToDel.id)
} catch (e: IOException) { }
if (e.message == "\"Not Found\"") { } catch (e: IOException) {
Log.d(TAG, "failed to delete currency on remote project : delete it locally anyway") if (e.message == "\"Not Found\"") {
dbHelper.deleteCurrency(cToDel.id) Log.d(TAG, "failed to delete currency on remote project : delete it locally anyway")
} else { dbHelper.deleteCurrency(cToDel.id)
throw e } else {
throw e
}
} }
} }
} else {
val currenciesToDelete = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_DELETED)
for (cToDel in currenciesToDelete) {
dbHelper.deleteCurrency(cToDel.id)
}
} }
val currenciesToEdit = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_EDITED) if (project.type == ProjectType.COSPEND) {
for (cToEdit in currenciesToEdit) { val currenciesToEdit = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_EDITED)
try { for (cToEdit in currenciesToEdit) {
val editRemoteCurrencyResponse = client!!.editRemoteCurrency(project, cToEdit) try {
if (editRemoteCurrencyResponse.stringContent == cToEdit.remoteId.toString()) { val editRemoteCurrencyResponse = client!!.editRemoteCurrency(project, cToEdit)
dbHelper.setCurrencyState(cToEdit.id, DBBill.STATE_OK) if (editRemoteCurrencyResponse.stringContent == cToEdit.remoteId.toString()) {
Log.d(TAG, "SUCCESSFUL remote currency edition (${editRemoteCurrencyResponse.stringContent})") dbHelper.setCurrencyState(cToEdit.id, DBBill.STATE_OK)
} else { Log.d(TAG, "SUCCESSFUL remote currency edition (${editRemoteCurrencyResponse.stringContent})")
Log.d(TAG, "FAILED to edit remote currency (${editRemoteCurrencyResponse.stringContent})") } else {
} Log.d(TAG, "FAILED to edit remote currency (${editRemoteCurrencyResponse.stringContent})")
} catch (e: IOException) { }
if (e.message == "{\"message\": \"Internal Server Error\"}") { } catch (e: IOException) {
Log.d(TAG, "FAILED to edit remote currency : it does not exist remotely") if (e.message == "{\"message\": \"Internal Server Error\"}") {
} else { Log.d(TAG, "FAILED to edit remote currency : it does not exist remotely")
throw e } else {
throw e
}
} }
} }
} else {
val currenciesToEdit = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_EDITED)
for (cToEdit in currenciesToEdit) {
dbHelper.setCurrencyState(cToEdit.id, DBBill.STATE_OK)
}
} }
val currencyToAdd = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_ADDED) if (project.type == ProjectType.COSPEND) {
for (cToAdd in currencyToAdd) { val currencyToAdd = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_ADDED)
val createRemoteCurrencyResponse = client!!.createRemoteCurrency(project, cToAdd) for (cToAdd in currencyToAdd) {
val newRemoteId = createRemoteCurrencyResponse.stringContent.toLong() val createRemoteCurrencyResponse = client!!.createRemoteCurrency(project, cToAdd)
if (newRemoteId > 0) { val newRemoteId = createRemoteCurrencyResponse.stringContent.toLong()
if (newRemoteId > 0) {
dbHelper.setCurrencyState(cToAdd.id, DBBill.STATE_OK)
}
}
} else {
val currencyToAdd = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_ADDED)
for (cToAdd in currencyToAdd) {
dbHelper.setCurrencyState(cToAdd.id, DBBill.STATE_OK) dbHelper.setCurrencyState(cToAdd.id, DBBill.STATE_OK)
} }
} }