- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
194 changed files
with
11,466 additions
and
11,149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
301 changes: 301 additions & 0 deletions
301
Android/EasyBudget/app/src/main/java/com/benoitletondor/easybudgetapp/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,301 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.benoitletondor.easybudgetapp | ||
|
||
import android.content.Intent | ||
import android.graphics.Color | ||
import android.os.Bundle | ||
import androidx.activity.SystemBarStyle | ||
import androidx.activity.compose.setContent | ||
import androidx.activity.enableEdgeToEdge | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.lifecycle.lifecycleScope | ||
import com.benoitletondor.easybudgetapp.compose.AppNavHost | ||
import com.benoitletondor.easybudgetapp.compose.AppTheme | ||
import com.benoitletondor.easybudgetapp.helper.Logger | ||
import com.benoitletondor.easybudgetapp.helper.MutableLiveFlow | ||
import com.benoitletondor.easybudgetapp.helper.centerButtons | ||
import com.benoitletondor.easybudgetapp.iab.Iab | ||
import com.benoitletondor.easybudgetapp.iab.PremiumCheckStatus | ||
import com.benoitletondor.easybudgetapp.parameters.Parameters | ||
import com.benoitletondor.easybudgetapp.parameters.getNumberOfDailyOpen | ||
import com.benoitletondor.easybudgetapp.parameters.getPremiumPopupLastAutoShowTimestamp | ||
import com.benoitletondor.easybudgetapp.parameters.getRatingPopupLastAutoShowTimestamp | ||
import com.benoitletondor.easybudgetapp.parameters.hasPremiumPopupBeenShow | ||
import com.benoitletondor.easybudgetapp.parameters.hasUserCompleteRating | ||
import com.benoitletondor.easybudgetapp.parameters.setPremiumPopupLastAutoShowTimestamp | ||
import com.benoitletondor.easybudgetapp.parameters.setPremiumPopupShown | ||
import com.benoitletondor.easybudgetapp.parameters.setRatingPopupLastAutoShowTimestamp | ||
import com.benoitletondor.easybudgetapp.view.RatingPopup | ||
import com.benoitletondor.easybudgetapp.view.getRatingPopupUserStep | ||
import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withContext | ||
import java.util.Calendar | ||
import java.util.Date | ||
import javax.inject.Inject | ||
|
||
/** | ||
* Main activity of the app | ||
* | ||
* @author Benoit LETONDOR | ||
*/ | ||
@AndroidEntryPoint | ||
class MainActivity : AppCompatActivity() { | ||
@Inject lateinit var parameters: Parameters | ||
@Inject lateinit var iab: Iab | ||
|
||
private val openSubscriptionScreenLiveFlow = MutableLiveFlow<Unit>() | ||
private val openAddExpenseScreenLiveFlow = MutableLiveFlow<Unit>() | ||
private val openAddRecurringExpenseScreenLiveFlow = MutableLiveFlow<Unit>() | ||
private val openMonthlyReportScreenLiveFlow = MutableLiveFlow<Unit>() | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
setTheme(R.style.AppTheme) | ||
super.onCreate(savedInstanceState) | ||
|
||
enableEdgeToEdge( | ||
statusBarStyle = SystemBarStyle.dark( | ||
scrim = Color.TRANSPARENT, | ||
) | ||
) | ||
|
||
setContent { | ||
AppTheme { | ||
AppNavHost( | ||
closeApp = { | ||
finish() | ||
}, | ||
openSubscriptionScreenFlow = openSubscriptionScreenLiveFlow, | ||
openAddExpenseScreenLiveFlow = openAddExpenseScreenLiveFlow, | ||
openAddRecurringExpenseScreenLiveFlow = openAddRecurringExpenseScreenLiveFlow, | ||
openMonthlyReportScreenFlow = openMonthlyReportScreenLiveFlow, | ||
) | ||
} | ||
} | ||
} | ||
|
||
override fun onResume() { | ||
super.onResume() | ||
|
||
showPremiumPopupIfNeeded() | ||
showRatingPopupIfNeeded() | ||
|
||
performIntentActionIfAny() | ||
} | ||
|
||
private fun showPremiumPopupIfNeeded() { | ||
lifecycleScope.launch { | ||
try { | ||
if ( parameters.hasPremiumPopupBeenShow() ) { | ||
return@launch | ||
} | ||
|
||
if ( iab.isUserPremium() || iab.iabStatusFlow.value == PremiumCheckStatus.ERROR ) { | ||
return@launch | ||
} | ||
|
||
if ( !parameters.hasUserCompleteRating() ) { | ||
return@launch | ||
} | ||
|
||
val currentStep = parameters.getRatingPopupUserStep() | ||
if (currentStep == RatingPopup.RatingPopupStep.STEP_LIKE || | ||
currentStep == RatingPopup.RatingPopupStep.STEP_LIKE_NOT_RATED || | ||
currentStep == RatingPopup.RatingPopupStep.STEP_LIKE_RATED) { | ||
if ( !hasRatingPopupBeenShownToday() && shouldShowPremiumPopup() ) { | ||
parameters.setPremiumPopupLastAutoShowTimestamp(Date().time) | ||
|
||
withContext(Dispatchers.Main) { | ||
MaterialAlertDialogBuilder(this@MainActivity) | ||
.setTitle(R.string.premium_popup_become_title) | ||
.setMessage(R.string.premium_popup_become_message) | ||
.setPositiveButton(R.string.premium_popup_become_cta) { dialog13, _ -> | ||
lifecycleScope.launch { | ||
openSubscriptionScreenLiveFlow.emit(Unit) | ||
} | ||
|
||
dialog13.dismiss() | ||
} | ||
.setNegativeButton(R.string.premium_popup_become_not_now) { dialog12, _ -> dialog12.dismiss() } | ||
.setNeutralButton(R.string.premium_popup_become_not_ask_again) { dialog1, _ -> | ||
parameters.setPremiumPopupShown() | ||
dialog1.dismiss() | ||
} | ||
.show() | ||
.centerButtons() | ||
} | ||
} | ||
} | ||
} catch (e: Exception) { | ||
Logger.error("Error while showing become premium popup", e) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Show the rating popup if the user didn't asked not to every day after the app has been open | ||
* in 3 different days. | ||
*/ | ||
private fun showRatingPopupIfNeeded() { | ||
try { | ||
val dailyOpens = parameters.getNumberOfDailyOpen() | ||
if (dailyOpens > 2) { | ||
if (!hasRatingPopupBeenShownToday()) { | ||
val shown = RatingPopup(this, parameters).show(false) | ||
if (shown) { | ||
parameters.setRatingPopupLastAutoShowTimestamp(Date().time) | ||
} | ||
} | ||
} | ||
} catch (e: Exception) { | ||
Logger.error("Error while showing rating popup", e) | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Has the rating popup been shown automatically today | ||
* | ||
* @return true if the rating popup has been shown today, false otherwise | ||
*/ | ||
private fun hasRatingPopupBeenShownToday(): Boolean { | ||
val lastRatingTS = parameters.getRatingPopupLastAutoShowTimestamp() | ||
if (lastRatingTS > 0) { | ||
val cal = Calendar.getInstance() | ||
val currentDay = cal.get(Calendar.DAY_OF_YEAR) | ||
|
||
cal.time = Date(lastRatingTS) | ||
val lastTimeDay = cal.get(Calendar.DAY_OF_YEAR) | ||
|
||
return currentDay == lastTimeDay | ||
} | ||
|
||
return false | ||
} | ||
|
||
/** | ||
* Check that last time the premium popup was shown was 2 days ago or more | ||
* | ||
* @return true if we can show premium popup, false otherwise | ||
*/ | ||
private fun shouldShowPremiumPopup(): Boolean { | ||
val lastPremiumTS = parameters.getPremiumPopupLastAutoShowTimestamp() | ||
if (lastPremiumTS == 0L) { | ||
return true | ||
} | ||
|
||
// Set calendar to last time 00:00 + 2 days | ||
val cal = Calendar.getInstance() | ||
cal.time = Date(lastPremiumTS) | ||
cal.set(Calendar.HOUR, 0) | ||
cal.set(Calendar.MINUTE, 0) | ||
cal.set(Calendar.SECOND, 0) | ||
cal.set(Calendar.MILLISECOND, 0) | ||
cal.add(Calendar.DAY_OF_YEAR, 2) | ||
|
||
return Date().after(cal.time) | ||
} | ||
|
||
private fun performIntentActionIfAny(): Boolean { | ||
if (intent != null) { | ||
return try { | ||
openMonthlyReportIfNeeded(intent) || openAddExpenseIfNeeded(intent) || openAddRecurringExpenseIfNeeded(intent) | ||
} finally { | ||
intent = null | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
override fun onNewIntent(intent: Intent) { | ||
super.onNewIntent(intent) | ||
|
||
this.intent = intent | ||
performIntentActionIfAny() | ||
} | ||
|
||
// ------------------------------------------> | ||
|
||
/** | ||
* Open the monthly report activity if the given intent contains the monthly uri part. | ||
* | ||
* @param intent | ||
*/ | ||
private fun openMonthlyReportIfNeeded(intent: Intent): Boolean { | ||
try { | ||
val data = intent.data | ||
if (data != null && "true" == data.getQueryParameter("monthly")) { | ||
lifecycleScope.launch { | ||
openMonthlyReportScreenLiveFlow.emit(Unit) | ||
} | ||
|
||
return true | ||
} | ||
} catch (e: Exception) { | ||
Logger.error("Error while opening report activity", e) | ||
} | ||
|
||
return false | ||
} | ||
|
||
|
||
/** | ||
* Open the add expense screen if the given intent contains the [.INTENT_SHOW_ADD_EXPENSE] | ||
* extra. | ||
* | ||
* @param intent | ||
*/ | ||
private fun openAddExpenseIfNeeded(intent: Intent): Boolean { | ||
if (intent.getBooleanExtra(INTENT_SHOW_ADD_EXPENSE, false)) { | ||
lifecycleScope.launch { | ||
openAddExpenseScreenLiveFlow.emit(Unit) | ||
} | ||
|
||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
/** | ||
* Open the add recurring expense screen if the given intent contains the [.INTENT_SHOW_ADD_RECURRING_EXPENSE] | ||
* extra. | ||
* | ||
* @param intent | ||
*/ | ||
private fun openAddRecurringExpenseIfNeeded(intent: Intent): Boolean { | ||
if (intent.getBooleanExtra(INTENT_SHOW_ADD_RECURRING_EXPENSE, false)) { | ||
lifecycleScope.launch { | ||
openAddRecurringExpenseScreenLiveFlow.emit(Unit) | ||
} | ||
|
||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
companion object { | ||
// Those 2 are used by the shortcuts | ||
private const val INTENT_SHOW_ADD_EXPENSE = "intent.addexpense.show" | ||
private const val INTENT_SHOW_ADD_RECURRING_EXPENSE = "intent.addrecurringexpense.show" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
459 changes: 459 additions & 0 deletions
459
Android/EasyBudget/app/src/main/java/com/benoitletondor/easybudgetapp/compose/AppNavHost.kt
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
...oid/EasyBudget/app/src/main/java/com/benoitletondor/easybudgetapp/compose/AppTopAppBar.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.compose | ||
|
||
import androidx.compose.foundation.clickable | ||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.ColumnScope | ||
import androidx.compose.foundation.layout.RowScope | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.shape.CircleShape | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||
import androidx.compose.material.icons.filled.MoreVert | ||
import androidx.compose.material3.DropdownMenu | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TopAppBar | ||
import androidx.compose.material3.TopAppBarDefaults | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.Immutable | ||
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.draw.clip | ||
import androidx.compose.ui.res.colorResource | ||
import androidx.compose.ui.unit.dp | ||
import com.benoitletondor.easybudgetapp.R | ||
|
||
sealed class BackButtonBehavior { | ||
data object Hidden : BackButtonBehavior() | ||
@Immutable | ||
data class NavigateBack(val onBackButtonPressed: () -> Unit) : BackButtonBehavior() | ||
} | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Composable | ||
fun AppTopAppBar( | ||
title: String, | ||
backButtonBehavior: BackButtonBehavior, | ||
actions: @Composable RowScope.() -> Unit = {}, | ||
) { | ||
TopAppBar( | ||
title = { | ||
Text( | ||
text = title, | ||
) | ||
}, | ||
actions = actions, | ||
navigationIcon = { | ||
if (backButtonBehavior is BackButtonBehavior.NavigateBack) { | ||
IconButton(onClick = backButtonBehavior.onBackButtonPressed) { | ||
Icon( | ||
imageVector = Icons.AutoMirrored.Filled.ArrowBack, | ||
contentDescription = "Up button", | ||
) | ||
} | ||
} | ||
}, | ||
colors = TopAppBarDefaults.topAppBarColors( | ||
containerColor = colorResource(id = R.color.action_bar_background), | ||
titleContentColor = colorResource(id = R.color.action_bar_text_color), | ||
actionIconContentColor = colorResource(id = R.color.action_bar_text_color), | ||
navigationIconContentColor = colorResource(id = R.color.action_bar_text_color), | ||
), | ||
) | ||
} | ||
|
||
@Composable | ||
fun AppTopBarMoreMenuItem(content: @Composable ColumnScope.(dismiss: () -> Unit) -> Unit) { | ||
var showMenu by remember { mutableStateOf(false) } | ||
|
||
Box( | ||
modifier = Modifier | ||
.clip(CircleShape) | ||
.clickable { showMenu = true } | ||
.padding(all = 8.dp), | ||
) { | ||
Icon( | ||
Icons.Default.MoreVert, | ||
contentDescription = "Menu", | ||
) | ||
DropdownMenu( | ||
expanded = showMenu, | ||
onDismissRequest = { showMenu = false } | ||
) { | ||
content { showMenu = false } | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
...et/app/src/main/java/com/benoitletondor/easybudgetapp/compose/AppWithTopAppBarScaffold.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.compose | ||
|
||
import androidx.compose.foundation.layout.PaddingValues | ||
import androidx.compose.foundation.layout.RowScope | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.runtime.Composable | ||
|
||
@Composable | ||
fun AppWithTopAppBarScaffold( | ||
title: String, | ||
backButtonBehavior: BackButtonBehavior, | ||
actions: @Composable RowScope.() -> Unit = {}, | ||
content: @Composable (PaddingValues) -> Unit, | ||
) { | ||
Scaffold( | ||
topBar = { | ||
AppTopAppBar( | ||
title = title, | ||
backButtonBehavior = backButtonBehavior, | ||
actions = actions, | ||
) | ||
}, | ||
content = content, | ||
) | ||
} |
50 changes: 50 additions & 0 deletions
50
...yBudget/app/src/main/java/com/benoitletondor/easybudgetapp/compose/PushPermissionState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.compose | ||
|
||
import android.Manifest | ||
import android.os.Build | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import com.google.accompanist.permissions.ExperimentalPermissionsApi | ||
import com.google.accompanist.permissions.PermissionState | ||
import com.google.accompanist.permissions.PermissionStatus | ||
import com.google.accompanist.permissions.rememberPermissionState | ||
|
||
private val isAndroid33OrMore = Build.VERSION.SDK_INT >= 33 | ||
|
||
@OptIn(ExperimentalPermissionsApi::class) | ||
@Composable | ||
fun rememberPermissionStateCompat( | ||
onPermissionResult: (Boolean) -> Unit, | ||
) : PermissionState { | ||
return if (isAndroid33OrMore) { | ||
rememberPermissionState( | ||
Manifest.permission.POST_NOTIFICATIONS, | ||
onPermissionResult = onPermissionResult, | ||
) | ||
} else { | ||
remember { | ||
object : PermissionState { | ||
override val permission: String = "android.permission.POST_NOTIFICATIONS" | ||
override val status: PermissionStatus = PermissionStatus.Granted | ||
override fun launchPermissionRequest() { | ||
onPermissionResult(true) | ||
} | ||
} | ||
} | ||
} | ||
} |
148 changes: 148 additions & 0 deletions
148
...src/main/java/com/benoitletondor/easybudgetapp/compose/components/ExpenseEditTextField.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.compose.components | ||
|
||
import androidx.compose.foundation.interaction.MutableInteractionSource | ||
import androidx.compose.foundation.layout.PaddingValues | ||
import androidx.compose.foundation.layout.defaultMinSize | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.text.BasicTextField | ||
import androidx.compose.foundation.text.KeyboardActions | ||
import androidx.compose.foundation.text.KeyboardOptions | ||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors | ||
import androidx.compose.foundation.text.selection.TextSelectionColors | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextFieldColors | ||
import androidx.compose.material3.TextFieldDefaults | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.CompositionLocalProvider | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.graphics.Color | ||
import androidx.compose.ui.graphics.Shape | ||
import androidx.compose.ui.graphics.SolidColor | ||
import androidx.compose.ui.res.colorResource | ||
import androidx.compose.ui.text.TextStyle | ||
import androidx.compose.ui.text.input.TextFieldValue | ||
import androidx.compose.ui.text.input.VisualTransformation | ||
import androidx.compose.ui.unit.dp | ||
import androidx.compose.ui.unit.sp | ||
import com.benoitletondor.easybudgetapp.R | ||
|
||
@OptIn(ExperimentalMaterial3Api::class) | ||
@Composable | ||
fun ExpenseEditTextField( | ||
value: TextFieldValue, | ||
onValueChange: (TextFieldValue) -> Unit, | ||
modifier: Modifier = Modifier, | ||
enabled: Boolean = true, | ||
readOnly: Boolean = false, | ||
textStyle: TextStyle = TextStyle( | ||
color = Color.White, | ||
fontSize = 17.sp, | ||
), | ||
label: String, | ||
placeholder: @Composable (() -> Unit)? = null, | ||
leadingIcon: @Composable (() -> Unit)? = null, | ||
trailingIcon: @Composable (() -> Unit)? = null, | ||
prefix: @Composable (() -> Unit)? = null, | ||
suffix: @Composable (() -> Unit)? = null, | ||
supportingText: @Composable (() -> Unit)? = null, | ||
isError: Boolean = false, | ||
visualTransformation: VisualTransformation = VisualTransformation.None, | ||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, | ||
keyboardActions: KeyboardActions = KeyboardActions.Default, | ||
singleLine: Boolean = true, | ||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, | ||
minLines: Int = 1, | ||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, | ||
shape: Shape = TextFieldDefaults.shape, | ||
colors: TextFieldColors = TextFieldDefaults.colors( | ||
focusedContainerColor = colorResource(R.color.action_bar_background), | ||
unfocusedContainerColor = colorResource(R.color.action_bar_background), | ||
errorContainerColor = colorResource(R.color.action_bar_background), | ||
cursorColor = Color.White, | ||
focusedLabelColor = Color.White, | ||
unfocusedLabelColor = Color.White, | ||
errorLabelColor = colorResource(R.color.budget_red), | ||
focusedTextColor = Color.White, | ||
unfocusedTextColor = Color.White, | ||
errorTextColor = Color.White, | ||
focusedIndicatorColor = colorResource(R.color.expense_edit_field_accent_color_dark), | ||
unfocusedIndicatorColor = colorResource(R.color.expense_edit_field_accent_color_dark), | ||
errorIndicatorColor = colorResource(R.color.expense_edit_field_accent_color_dark), | ||
), | ||
) { | ||
val textColor = textStyle.color | ||
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor)) | ||
|
||
val customTextSelectionColors = TextSelectionColors( | ||
handleColor = Color.White, | ||
backgroundColor = Color.White.copy(alpha = 0.4f) | ||
) | ||
|
||
CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { | ||
BasicTextField( | ||
value = value, | ||
modifier = modifier | ||
.defaultMinSize( | ||
minWidth = TextFieldDefaults.MinWidth, | ||
minHeight = TextFieldDefaults.MinHeight + 4.dp, | ||
), | ||
onValueChange = onValueChange, | ||
enabled = enabled, | ||
readOnly = readOnly, | ||
textStyle = mergedTextStyle, | ||
cursorBrush = SolidColor(Color.White), | ||
visualTransformation = visualTransformation, | ||
keyboardOptions = keyboardOptions, | ||
keyboardActions = keyboardActions, | ||
interactionSource = interactionSource, | ||
singleLine = singleLine, | ||
maxLines = maxLines, | ||
minLines = minLines, | ||
decorationBox = @Composable { innerTextField -> | ||
// places leading icon, text field with label and placeholder, trailing icon | ||
TextFieldDefaults.DecorationBox( | ||
value = value.text, | ||
visualTransformation = visualTransformation, | ||
innerTextField = innerTextField, | ||
placeholder = placeholder, | ||
label = { | ||
Text( | ||
modifier = Modifier.padding(bottom = 4.dp), | ||
text = label, | ||
fontSize = 15.sp, | ||
) | ||
}, | ||
leadingIcon = leadingIcon, | ||
trailingIcon = trailingIcon, | ||
prefix = prefix, | ||
suffix = suffix, | ||
supportingText = supportingText, | ||
shape = shape, | ||
singleLine = singleLine, | ||
enabled = enabled, | ||
isError = isError, | ||
interactionSource = interactionSource, | ||
colors = colors, | ||
contentPadding = PaddingValues(top = 7.dp), | ||
) | ||
} | ||
) | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
...dget/app/src/main/java/com/benoitletondor/easybudgetapp/compose/components/LoadingView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.compose.components | ||
|
||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
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.padding | ||
import androidx.compose.material3.CircularProgressIndicator | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.text.style.TextAlign | ||
import androidx.compose.ui.unit.dp | ||
|
||
@Composable | ||
fun LoadingView( | ||
modifier: Modifier = Modifier, | ||
loadingText: String? = null, | ||
) { | ||
Column( | ||
verticalArrangement = Arrangement.Center, | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
modifier = modifier | ||
.fillMaxSize() | ||
.padding(horizontal = 20.dp), | ||
) { | ||
CircularProgressIndicator() | ||
|
||
if (loadingText != null) { | ||
Spacer(modifier = Modifier.height(10.dp)) | ||
|
||
Text( | ||
modifier = Modifier.fillMaxWidth(), | ||
textAlign = TextAlign.Center, | ||
text = loadingText, | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 0 additions & 40 deletions
40
Android/EasyBudget/app/src/main/java/com/benoitletondor/easybudgetapp/helper/BaseActivity.kt
This file was deleted.
Oops, something went wrong.
46 changes: 0 additions & 46 deletions
46
Android/EasyBudget/app/src/main/java/com/benoitletondor/easybudgetapp/helper/BaseFragment.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
...t/app/src/main/java/com/benoitletondor/easybudgetapp/helper/RecurringExpenseTypeHelper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.helper | ||
|
||
import android.content.Context | ||
import com.benoitletondor.easybudgetapp.R | ||
import com.benoitletondor.easybudgetapp.model.RecurringExpenseType | ||
|
||
fun RecurringExpenseType.stringRepresentation(context: Context): String { | ||
return when (this) { | ||
RecurringExpenseType.DAILY -> context.getString(R.string.recurring_interval_daily) | ||
RecurringExpenseType.WEEKLY -> context.getString(R.string.recurring_interval_weekly) | ||
RecurringExpenseType.BI_WEEKLY -> context.getString(R.string.recurring_interval_bi_weekly) | ||
RecurringExpenseType.TER_WEEKLY -> context.getString(R.string.recurring_interval_ter_weekly) | ||
RecurringExpenseType.FOUR_WEEKLY -> context.getString(R.string.recurring_interval_four_weekly) | ||
RecurringExpenseType.MONTHLY -> context.getString(R.string.recurring_interval_monthly) | ||
RecurringExpenseType.BI_MONTHLY -> context.getString(R.string.recurring_interval_bi_monthly) | ||
RecurringExpenseType.TER_MONTHLY -> context.getString(R.string.recurring_interval_ter_monthly) | ||
RecurringExpenseType.SIX_MONTHLY -> context.getString(R.string.recurring_interval_six_monthly) | ||
RecurringExpenseType.YEARLY -> context.getString(R.string.recurring_interval_yearly) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
.../src/main/java/com/benoitletondor/easybudgetapp/helper/serialization/SerializedExpense.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.helper.serialization | ||
|
||
import android.os.Parcelable | ||
import com.benoitletondor.easybudgetapp.model.AssociatedRecurringExpense | ||
import com.benoitletondor.easybudgetapp.model.Expense | ||
import com.benoitletondor.easybudgetapp.model.RecurringExpense | ||
import com.benoitletondor.easybudgetapp.model.RecurringExpenseType | ||
import kotlinx.parcelize.Parcelize | ||
import kotlinx.serialization.Serializable | ||
import java.time.LocalDate | ||
|
||
@Serializable | ||
@Parcelize | ||
data class SerializedExpense( | ||
val id: Long?, | ||
val title: String, | ||
val amount: Double, | ||
val date: Long, | ||
val checked: Boolean, | ||
val associatedRecurringExpense: SerializedAssociatedRecurringExpense? | ||
) : Parcelable { | ||
constructor(expense: Expense) : this( | ||
expense.id, | ||
expense.title.serializeForNavigation(), | ||
expense.amount, | ||
expense.date.toEpochDay(), | ||
expense.checked, | ||
expense.associatedRecurringExpense?.let { SerializedAssociatedRecurringExpense(it) }, | ||
) | ||
|
||
fun toExpense(): Expense = Expense( | ||
id, | ||
title.deserializeForNavigation(), | ||
amount, | ||
LocalDate.ofEpochDay(date), | ||
checked, | ||
associatedRecurringExpense?.toAssociatedRecurringExpense(), | ||
) | ||
} | ||
|
||
@Serializable | ||
@Parcelize | ||
data class SerializedAssociatedRecurringExpense( | ||
val recurringExpense: SerializedRecurringExpense, | ||
val originalDate: Long, | ||
) : Parcelable { | ||
constructor(associatedRecurringExpense: AssociatedRecurringExpense) : this( | ||
SerializedRecurringExpense(associatedRecurringExpense.recurringExpense), | ||
associatedRecurringExpense.originalDate.toEpochDay(), | ||
) | ||
|
||
fun toAssociatedRecurringExpense() = AssociatedRecurringExpense( | ||
recurringExpense.toRecurringExpense(), | ||
LocalDate.ofEpochDay(originalDate), | ||
) | ||
} | ||
|
||
@Serializable | ||
@Parcelize | ||
data class SerializedRecurringExpense( | ||
val id: Long?, | ||
val title: String, | ||
val amount: Double, | ||
val recurringDate: Long, | ||
val modified: Boolean, | ||
val type: RecurringExpenseType | ||
) : Parcelable { | ||
constructor(recurringExpense: RecurringExpense) : this( | ||
recurringExpense.id, | ||
recurringExpense.title.serializeForNavigation(), | ||
recurringExpense.amount, | ||
recurringExpense.recurringDate.toEpochDay(), | ||
recurringExpense.modified, | ||
recurringExpense.type, | ||
) | ||
|
||
fun toRecurringExpense() = RecurringExpense( | ||
id, | ||
title.deserializeForNavigation(), | ||
amount, | ||
LocalDate.ofEpochDay(recurringDate), | ||
modified, | ||
type, | ||
) | ||
} |
34 changes: 34 additions & 0 deletions
34
.../com/benoitletondor/easybudgetapp/helper/serialization/SerializedSelectedOnlineAccount.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.benoitletondor.easybudgetapp.helper.serialization | ||
|
||
import android.os.Parcelable | ||
import com.benoitletondor.easybudgetapp.view.main.MainViewModel | ||
import kotlinx.parcelize.Parcelize | ||
import kotlinx.serialization.Serializable | ||
import java.net.URLDecoder | ||
import java.net.URLEncoder | ||
|
||
@Serializable | ||
@Parcelize | ||
data class SerializedSelectedOnlineAccount( | ||
val name: String, | ||
val isOwner: Boolean, | ||
val ownerEmail: String, | ||
val accountId: String, | ||
val accountSecret: String, | ||
) : Parcelable { | ||
constructor(selectedAccount: MainViewModel.SelectedAccount.Selected.Online) : this( | ||
URLEncoder.encode(selectedAccount.name, "UTF-8"), | ||
selectedAccount.isOwner, | ||
selectedAccount.ownerEmail, | ||
selectedAccount.accountId, | ||
selectedAccount.accountSecret, | ||
) | ||
|
||
fun toSelectedAccount() = MainViewModel.SelectedAccount.Selected.Online( | ||
URLDecoder.decode(name, "UTF-8"), | ||
isOwner, | ||
ownerEmail, | ||
accountId, | ||
accountSecret, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
...in/java/com/benoitletondor/easybudgetapp/helper/serialization/StringSanitizationHelper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* Copyright 2024 Benoit LETONDOR | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package com.benoitletondor.easybudgetapp.helper.serialization | ||
|
||
import kotlin.io.encoding.Base64 | ||
import kotlin.io.encoding.ExperimentalEncodingApi | ||
|
||
@OptIn(ExperimentalEncodingApi::class) | ||
fun String.serializeForNavigation() = Base64.UrlSafe.encode(this.encodeToByteArray()) | ||
|
||
@OptIn(ExperimentalEncodingApi::class) | ||
fun String.deserializeForNavigation() = Base64.UrlSafe.decode(this).decodeToString() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 0 additions & 51 deletions
51
...udget/app/src/main/java/com/benoitletondor/easybudgetapp/view/DatePickerDialogFragment.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
258 changes: 0 additions & 258 deletions
258
...pp/src/main/java/com/benoitletondor/easybudgetapp/view/expenseedit/ExpenseEditActivity.kt
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.