diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d52f9c0..436d858 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "com.d4rk.cartcalculator" minSdk = 26 targetSdk = 34 - versionCode = 54 - versionName = "1.0.6" + versionCode = 56 + versionName = "1.0.7" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" resourceConfigurations += listOf( "en", @@ -35,6 +35,7 @@ android { "bg", "pl", "uk", + "pt-rBR", ) vectorDrawables { useSupportLibrary = true @@ -47,7 +48,6 @@ android { isMinifyEnabled = true isShrinkResources = true isDebuggable = false - versionNameSuffix = "-release" proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) @@ -56,7 +56,6 @@ android { debug { multiDexEnabled = true isDebuggable = true - versionNameSuffix = "-debug" proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) @@ -91,6 +90,7 @@ android { } } } + dependencies { // Firebase implementation(platform(libs.firebase.bom)) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/MainComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/MainComposable.kt index cf91649..9d55fa2 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/MainComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/MainComposable.kt @@ -37,7 +37,7 @@ import com.d4rk.cartcalculator.ui.help.HelpActivity import com.d4rk.cartcalculator.ui.home.HomeComposable import com.d4rk.cartcalculator.ui.settings.SettingsActivity import com.d4rk.cartcalculator.ui.support.SupportActivity -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.IntentUtils import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -77,26 +77,26 @@ fun MainComposable() { when (item.title) { R.string.settings -> { - Utils.openActivity( + IntentUtils.openActivity( context, SettingsActivity::class.java ) } R.string.help_and_feedback -> { - Utils.openActivity( + IntentUtils.openActivity( context, HelpActivity::class.java ) } R.string.updates -> { - Utils.openUrl( + IntentUtils.openUrl( context, "https://github.com/D4rK7355608/${context.packageName}/blob/master/CHANGELOG.md" ) } R.string.share -> { - Utils.shareApp(context) + IntentUtils.shareApp(context) } } scope.launch { @@ -137,7 +137,7 @@ fun MainComposable() { } }, actions = { IconButton(onClick = { - Utils.openActivity(context, SupportActivity::class.java) + IntentUtils.openActivity(context, SupportActivity::class.java) }) { Icon( Icons.Outlined.VolunteerActivism, contentDescription = "Support" diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/constants/permissions/PermissionsConstants.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/constants/permissions/PermissionsConstants.kt new file mode 100644 index 0000000..61b8024 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/constants/permissions/PermissionsConstants.kt @@ -0,0 +1,5 @@ +package com.d4rk.cartcalculator.constants.permissions + +object PermissionsConstants { + const val REQUEST_CODE_NOTIFICATION_PERMISSION = 2 +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/data/model/ui/button/ButtonState.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/data/model/ui/button/ButtonState.kt new file mode 100644 index 0000000..4b98c4f --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/data/model/ui/button/ButtonState.kt @@ -0,0 +1,3 @@ +package com.d4rk.cartcalculator.data.model.ui.button + +enum class ButtonState { Pressed , Idle } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/cart/CartActivityComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/cart/CartActivityComposable.kt index 471b17b..8fd72d6 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/cart/CartActivityComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/cart/CartActivityComposable.kt @@ -54,7 +54,7 @@ import com.d4rk.cartcalculator.data.database.table.ShoppingCartItemsTable import com.d4rk.cartcalculator.data.datastore.DataStore import com.d4rk.cartcalculator.ui.dialogs.DeleteCartItemDialog import com.d4rk.cartcalculator.ui.dialogs.NewCartItemDialog -import com.d4rk.cartcalculator.utils.bounceClick +import com.d4rk.cartcalculator.utils.compose.bounceClick import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/dialogs/VersionInfoDialogComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/dialogs/VersionInfoDialogComposable.kt index 600dbb6..6998a64 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/dialogs/VersionInfoDialogComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/dialogs/VersionInfoDialogComposable.kt @@ -1,11 +1,5 @@ package com.d4rk.cartcalculator.ui.dialogs -import android.content.res.Resources -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.drawable.AdaptiveIconDrawable -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -25,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.BuildConfig import com.d4rk.cartcalculator.R +import com.d4rk.cartcalculator.utils.drawable.toBitmapDrawable @Composable fun VersionInfoDialog(onDismiss: () -> Unit) { @@ -73,20 +68,4 @@ fun VersionInfoContent() { ) } } -} - -fun Drawable.toBitmapDrawable(): BitmapDrawable { - return when (this) { - is BitmapDrawable -> this - is AdaptiveIconDrawable -> { - val bitmap = - Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - setBounds(0, 0, canvas.width, canvas.height) - draw(canvas) - BitmapDrawable(Resources.getSystem(), bitmap) - } - - else -> throw IllegalArgumentException("Unsupported drawable type: ${this::class.java.name}") - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpActivity.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpActivity.kt index dd2c38e..06f7202 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpActivity.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpActivity.kt @@ -3,56 +3,27 @@ package com.d4rk.cartcalculator.ui.help import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import com.d4rk.cartcalculator.ui.settings.display.theme.style.AppTheme -import com.d4rk.cartcalculator.utils.Utils -import com.google.android.play.core.review.ReviewManager -import com.google.android.play.core.review.ReviewManagerFactory class HelpActivity : AppCompatActivity() { - private lateinit var reviewManager: ReviewManager - override fun onCreate(savedInstanceState: Bundle?) { + private val viewModel: HelpViewModel by viewModels() + override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { AppTheme { Surface( - modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background ) { - HelpComposable(this@HelpActivity) + HelpComposable(this@HelpActivity, viewModel) } } } - - } - - /** - * Initiates the feedback process for the app. - * - * This function uses the Google Play In-App Review API to prompt the user for feedback. - * If the request to launch the in-app review flow is successful, the review dialog is displayed. - * If the request fails, it opens the Google Play Store page for the app's reviews. - * - * @see com.google.android.play.core.review.ReviewManagerFactory - * @see com.google.android.play.core.review.ReviewManager - * @param context The context used to create the ReviewManager instance and launch review flows. - */ - fun feedback() { - reviewManager = ReviewManagerFactory.create(this) - val task = reviewManager.requestReviewFlow() - task.addOnSuccessListener { reviewInfo -> - reviewManager.launchReviewFlow(this, reviewInfo) - }.addOnFailureListener { - Utils.openUrl( - this, - "https://play.google.com/store/apps/details?id=${this.packageName}&showAllReviews=true" - ) - }.addOnFailureListener { - Utils.sendEmailToDeveloper(this) - } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpComposable.kt index a909c55..c296b24 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpComposable.kt @@ -33,6 +33,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -46,80 +47,88 @@ import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.ui.dialogs.VersionInfoDialog -import com.d4rk.cartcalculator.utils.Utils -import com.d4rk.cartcalculator.utils.bounceClick +import com.d4rk.cartcalculator.utils.IntentUtils +import com.d4rk.cartcalculator.utils.compose.bounceClick import com.google.android.gms.oss.licenses.OssLicensesMenuActivity @OptIn(ExperimentalMaterial3Api::class) @Composable -fun HelpComposable(activity: HelpActivity) { +fun HelpComposable(activity : HelpActivity, viewModel: HelpViewModel) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) var showMenu by remember { mutableStateOf(false) } val context = LocalContext.current val showDialog = remember { mutableStateOf(false) } - Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - LargeTopAppBar(title = { Text(stringResource(R.string.help)) }, navigationIcon = { + val reviewInfo = viewModel.reviewInfo.value + + if (reviewInfo != null) { + LaunchedEffect(key1 = reviewInfo) { + viewModel.requestReviewFlow() + } + } + + Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) , topBar = { + LargeTopAppBar(title = { Text(stringResource(R.string.help)) } , navigationIcon = { IconButton(onClick = { activity.finish() }) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) + Icon(Icons.AutoMirrored.Filled.ArrowBack , contentDescription = null) } - }, actions = { + } , actions = { IconButton(onClick = { showMenu = true }) { - Icon(Icons.Default.MoreVert, contentDescription = "Localized description") + Icon(Icons.Default.MoreVert , contentDescription = "Localized description") } - DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { - DropdownMenuItem(text = { Text(stringResource(R.string.view_in_google_play_store)) }, + DropdownMenu(expanded = showMenu , onDismissRequest = { showMenu = false }) { + DropdownMenuItem(text = { Text(stringResource(R.string.view_in_google_play_store)) } , onClick = { - Utils.openUrl( - context, + IntentUtils.openUrl( + context , "https://play.google.com/store/apps/details?id=${activity.packageName}" ) }) - DropdownMenuItem(text = { Text(stringResource(R.string.version_info)) }, + DropdownMenuItem(text = { Text(stringResource(R.string.version_info)) } , onClick = { showDialog.value = true }) - DropdownMenuItem(text = { Text(stringResource(R.string.beta_program)) }, + DropdownMenuItem(text = { Text(stringResource(R.string.beta_program)) } , onClick = { - Utils.openUrl( - context, + IntentUtils.openUrl( + context , "https://play.google.com/apps/testing/${activity.packageName}" ) }) - DropdownMenuItem(text = { Text(stringResource(R.string.terms_of_service)) }, + DropdownMenuItem(text = { Text(stringResource(R.string.terms_of_service)) } , onClick = { - Utils.openUrl( - context, + IntentUtils.openUrl( + context , "https://sites.google.com/view/d4rk7355608/more/apps/terms-of-service" ) }) - DropdownMenuItem(text = { Text(stringResource(R.string.privacy_policy)) }, + DropdownMenuItem(text = { Text(stringResource(R.string.privacy_policy)) } , onClick = { - Utils.openUrl( - context, + IntentUtils.openUrl( + context , "https://sites.google.com/view/d4rk7355608/more/apps/privacy-policy" ) }) - DropdownMenuItem(text = { Text(stringResource(com.google.android.gms.oss.licenses.R.string.oss_license_title)) }, + DropdownMenuItem(text = { Text(stringResource(com.google.android.gms.oss.licenses.R.string.oss_license_title)) } , onClick = { - Utils.openActivity( - context, OssLicensesMenuActivity::class.java + IntentUtils.openActivity( + context , OssLicensesMenuActivity::class.java ) }) } if (showDialog.value) { VersionInfoDialog(onDismiss = { showDialog.value = false }) } - }, scrollBehavior = scrollBehavior) + } , scrollBehavior = scrollBehavior) }) { paddingValues -> Box( modifier = Modifier - .padding(start = 16.dp, end = 16.dp) + .padding(start = 16.dp , end = 16.dp) .fillMaxSize() .safeDrawingPadding() ) { ConstraintLayout(modifier = Modifier.padding(paddingValues)) { - val (faqTitle, faqCard) = createRefs() - Text(text = stringResource(R.string.faq), + val (faqTitle , faqCard) = createRefs() + Text(text = stringResource(R.string.faq) , modifier = Modifier .padding(bottom = 24.dp) .constrainAs(faqTitle) { @@ -136,19 +145,21 @@ fun HelpComposable(activity: HelpActivity) { } } ExtendedFloatingActionButton( - text = { Text(stringResource(id = R.string.feedback)) }, + text = { Text(stringResource(id = R.string.feedback)) } , onClick = { - activity.feedback() + viewModel.reviewInfo.value?.let { safeReviewInfo -> + viewModel.launchReviewFlow(activity, safeReviewInfo) + } }, icon = { Icon( - Icons.Default.MailOutline, contentDescription = null + Icons.Default.MailOutline , contentDescription = null ) - }, + } , modifier = Modifier .bounceClick() .padding(bottom = 16.dp) - .align(Alignment.BottomEnd), + .align(Alignment.BottomEnd) , ) } } @@ -159,55 +170,55 @@ fun FAQComposable() { LazyColumn { item { QuestionComposable( - title = stringResource(R.string.question_1), + title = stringResource(R.string.question_1) , summary = stringResource(R.string.summary_preference_faq_1) ) } item { QuestionComposable( - title = stringResource(R.string.question_2), + title = stringResource(R.string.question_2) , summary = stringResource(R.string.summary_preference_faq_2) ) } item { QuestionComposable( - title = stringResource(R.string.question_3), + title = stringResource(R.string.question_3) , summary = stringResource(R.string.summary_preference_faq_3) ) } item { QuestionComposable( - title = stringResource(R.string.question_4), + title = stringResource(R.string.question_4) , summary = stringResource(R.string.summary_preference_faq_4) ) } item { QuestionComposable( - title = stringResource(R.string.question_5), + title = stringResource(R.string.question_5) , summary = stringResource(R.string.summary_preference_faq_5) ) } item { QuestionComposable( - title = stringResource(R.string.question_6), + title = stringResource(R.string.question_6) , summary = stringResource(R.string.summary_preference_faq_6) ) } item { QuestionComposable( - title = stringResource(R.string.question_7), + title = stringResource(R.string.question_7) , summary = stringResource(R.string.summary_preference_faq_7) ) } item { QuestionComposable( - title = stringResource(R.string.question_8), + title = stringResource(R.string.question_8) , summary = stringResource(R.string.summary_preference_faq_8) ) } item { QuestionComposable( - title = stringResource(R.string.question_9), + title = stringResource(R.string.question_9) , summary = stringResource(R.string.summary_preference_faq_9) ) } @@ -215,27 +226,27 @@ fun FAQComposable() { } @Composable -fun QuestionComposable(title: String, summary: String) { +fun QuestionComposable(title : String , summary : String) { Row( modifier = Modifier .fillMaxWidth() - .padding(16.dp), + .padding(16.dp) , verticalAlignment = Alignment.CenterVertically ) { Icon( - Icons.Outlined.QuestionAnswer, - contentDescription = null, + Icons.Outlined.QuestionAnswer , + contentDescription = null , modifier = Modifier .size(48.dp) .background( - color = MaterialTheme.colorScheme.primaryContainer, shape = CircleShape + color = MaterialTheme.colorScheme.primaryContainer , shape = CircleShape ) .padding(8.dp) ) Spacer(modifier = Modifier.width(16.dp)) Column { Text( - text = title, style = MaterialTheme.typography.titleMedium + text = title , style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(4.dp)) Text(text = summary) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpViewModel.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpViewModel.kt new file mode 100644 index 0000000..30ffa72 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/help/HelpViewModel.kt @@ -0,0 +1,35 @@ +package com.d4rk.cartcalculator.ui.help + +import android.app.Application +import androidx.compose.runtime.* +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.play.core.review.ReviewInfo +import com.google.android.play.core.review.ReviewManagerFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class HelpViewModel(application: Application) : AndroidViewModel(application) { + + private var _reviewInfo: MutableState = mutableStateOf(null) + val reviewInfo: State = _reviewInfo + + fun requestReviewFlow() { + viewModelScope.launch(Dispatchers.IO) { + val reviewManager = ReviewManagerFactory.create(getApplication()) + val request = reviewManager.requestReviewFlow() + request.addOnCompleteListener { task -> + if (task.isSuccessful) { + _reviewInfo.value = task.result + } else { + task.exception?.printStackTrace() + } + } + } + } + + fun launchReviewFlow(activity: HelpActivity, reviewInfo: ReviewInfo) { + val reviewManager = ReviewManagerFactory.create(activity) + reviewManager.launchReviewFlow(activity, reviewInfo) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/home/HomeComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/home/HomeComposable.kt index bfd3286..56bb64b 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/home/HomeComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/home/HomeComposable.kt @@ -44,10 +44,10 @@ import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.ads.BannerAdsComposable import com.d4rk.cartcalculator.data.database.table.ShoppingCartTable import com.d4rk.cartcalculator.data.datastore.DataStore +import com.d4rk.cartcalculator.ui.cart.CartActivity import com.d4rk.cartcalculator.ui.dialogs.DeleteCartDialog import com.d4rk.cartcalculator.ui.dialogs.NewCartDialog -import com.d4rk.cartcalculator.ui.cart.CartActivity -import com.d4rk.cartcalculator.utils.bounceClick +import com.d4rk.cartcalculator.utils.compose.bounceClick import java.text.SimpleDateFormat import java.util.Date import java.util.Locale diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/SettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/SettingsComposable.kt index 69ddddf..fe4acc9 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/SettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/SettingsComposable.kt @@ -30,8 +30,8 @@ import com.d4rk.cartcalculator.ui.settings.advanced.AdvancedSettingsActivity import com.d4rk.cartcalculator.ui.settings.cart.CartSettingsActivity import com.d4rk.cartcalculator.ui.settings.display.DisplaySettingsActivity import com.d4rk.cartcalculator.ui.settings.privacy.PrivacySettingsActivity -import com.d4rk.cartcalculator.utils.PreferenceItem -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem +import com.d4rk.cartcalculator.utils.IntentUtils @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -57,7 +57,7 @@ fun SettingsComposable(activity: SettingsActivity) { title = stringResource(R.string.display), summary = stringResource(R.string.summary_preference_settings_display), onClick = { - Utils.openActivity(context, DisplaySettingsActivity::class.java) + IntentUtils.openActivity(context, DisplaySettingsActivity::class.java) }) } item { @@ -65,7 +65,7 @@ fun SettingsComposable(activity: SettingsActivity) { title = stringResource(R.string.cart_settings), summary = stringResource(R.string.summary_preference_settings_cart), onClick = { - Utils.openActivity(context, CartSettingsActivity::class.java) + IntentUtils.openActivity(context, CartSettingsActivity::class.java) }) } item { @@ -73,7 +73,7 @@ fun SettingsComposable(activity: SettingsActivity) { title = stringResource(R.string.notifications), summary = stringResource(R.string.summary_preference_settings_notifications), onClick = { - Utils.openAppNotificationSettings(context) + IntentUtils.openAppNotificationSettings(context) }) } item { @@ -81,7 +81,7 @@ fun SettingsComposable(activity: SettingsActivity) { title = stringResource(R.string.advanced), summary = stringResource(R.string.summary_preference_settings_advanced), onClick = { - Utils.openActivity( + IntentUtils.openActivity( context, AdvancedSettingsActivity::class.java ) }) @@ -91,7 +91,7 @@ fun SettingsComposable(activity: SettingsActivity) { title = stringResource(R.string.security_and_privacy), summary = stringResource(R.string.summary_preference_settings_privacy_and_security), onClick = { - Utils.openActivity(context, PrivacySettingsActivity::class.java) + IntentUtils.openActivity(context, PrivacySettingsActivity::class.java) }) } item { @@ -99,7 +99,7 @@ fun SettingsComposable(activity: SettingsActivity) { title = stringResource(R.string.about), summary = stringResource(R.string.summary_preference_settings_about), onClick = { - Utils.openActivity(context, AboutSettingsActivity::class.java) + IntentUtils.openActivity(context, AboutSettingsActivity::class.java) }) } } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/about/AboutSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/about/AboutSettingsComposable.kt index 67a77b8..1db5fe7 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/about/AboutSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/about/AboutSettingsComposable.kt @@ -24,9 +24,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.d4rk.cartcalculator.BuildConfig import com.d4rk.cartcalculator.R -import com.d4rk.cartcalculator.utils.PreferenceCategoryItem -import com.d4rk.cartcalculator.utils.PreferenceItem -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.PreferenceCategoryItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem +import com.d4rk.cartcalculator.utils.IntentUtils import com.google.android.gms.oss.licenses.OssLicensesMenuActivity @OptIn(ExperimentalMaterial3Api::class) @@ -62,7 +62,7 @@ fun AboutSettingsComposable(activity: AboutSettingsActivity) { PreferenceItem(title = stringResource(com.google.android.gms.oss.licenses.R.string.oss_license_title), summary = stringResource(R.string.summary_preference_settings_oss), onClick = { - Utils.openActivity(context, OssLicensesMenuActivity::class.java) + IntentUtils.openActivity(context, OssLicensesMenuActivity::class.java) }) } item { diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/advanced/AdvancedSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/advanced/AdvancedSettingsComposable.kt index 0816143..3d668d6 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/advanced/AdvancedSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/advanced/AdvancedSettingsComposable.kt @@ -19,9 +19,9 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.d4rk.cartcalculator.R -import com.d4rk.cartcalculator.utils.PreferenceCategoryItem -import com.d4rk.cartcalculator.utils.PreferenceItem -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.PreferenceCategoryItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem +import com.d4rk.cartcalculator.utils.IntentUtils @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -49,7 +49,7 @@ fun AdvancedSettingsComposable(activity: AdvancedSettingsActivity) { PreferenceItem(title = stringResource(R.string.bug_report), summary = stringResource(R.string.summary_preference_settings_bug_report), onClick = { - Utils.openUrl( + IntentUtils.openUrl( context, "https://github.com/D4rK7355608/${context.packageName}/issues/new" ) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/cart/CartSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/cart/CartSettingsComposable.kt index efe1b82..0c1a76d 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/cart/CartSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/cart/CartSettingsComposable.kt @@ -23,8 +23,8 @@ import androidx.compose.ui.res.stringResource import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.data.datastore.DataStore import com.d4rk.cartcalculator.ui.dialogs.CurrencyDialog -import com.d4rk.cartcalculator.utils.PreferenceCategoryItem -import com.d4rk.cartcalculator.utils.PreferenceItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceCategoryItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/DisplaySettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/DisplaySettingsComposable.kt index e268156..6a7ef11 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/DisplaySettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/DisplaySettingsComposable.kt @@ -34,11 +34,11 @@ import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.data.datastore.DataStore import com.d4rk.cartcalculator.ui.dialogs.LanguageDialog import com.d4rk.cartcalculator.ui.settings.display.theme.ThemeSettingsActivity -import com.d4rk.cartcalculator.utils.PreferenceCategoryItem -import com.d4rk.cartcalculator.utils.PreferenceItem -import com.d4rk.cartcalculator.utils.SwitchPreferenceItem -import com.d4rk.cartcalculator.utils.SwitchPreferenceItemWithDivider -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.PreferenceCategoryItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem +import com.d4rk.cartcalculator.utils.compose.components.SwitchPreferenceItem +import com.d4rk.cartcalculator.utils.compose.components.SwitchPreferenceItemWithDivider +import com.d4rk.cartcalculator.utils.IntentUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -98,7 +98,7 @@ fun DisplaySettingsComposable(activity: DisplaySettingsActivity) { } }, onClick = { - Utils.openActivity( + IntentUtils.openActivity( context, ThemeSettingsActivity::class.java ) }) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/theme/ThemeSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/theme/ThemeSettingsComposable.kt index f82e5ad..111c2fa 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/theme/ThemeSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/display/theme/ThemeSettingsComposable.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.data.datastore.DataStore -import com.d4rk.cartcalculator.utils.SwitchCardComposable +import com.d4rk.cartcalculator.utils.compose.components.SwitchCardComposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/PrivacySettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/PrivacySettingsComposable.kt index e565d4c..8874300 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/PrivacySettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/PrivacySettingsComposable.kt @@ -22,9 +22,9 @@ import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.ui.settings.privacy.ads.AdsSettingsActivity import com.d4rk.cartcalculator.ui.settings.privacy.permissions.PermissionsSettingsActivity import com.d4rk.cartcalculator.ui.settings.privacy.usage.UsageAndDiagnosticsActivity -import com.d4rk.cartcalculator.utils.PreferenceCategoryItem -import com.d4rk.cartcalculator.utils.PreferenceItem -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.PreferenceCategoryItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem +import com.d4rk.cartcalculator.utils.IntentUtils @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -55,7 +55,7 @@ fun PrivacySettingsComposable(activity: PrivacySettingsActivity) { PreferenceItem(title = stringResource(R.string.privacy_policy), summary = stringResource(id = R.string.summary_preference_settings_privacy_policy), onClick = { - Utils.openUrl( + IntentUtils.openUrl( context, "https://sites.google.com/view/d4rk7355608/more/apps/privacy-policy" ) @@ -63,7 +63,7 @@ fun PrivacySettingsComposable(activity: PrivacySettingsActivity) { PreferenceItem(title = stringResource(R.string.terms_of_service), summary = stringResource(id = R.string.summary_preference_settings_terms_of_service), onClick = { - Utils.openUrl( + IntentUtils.openUrl( context, "https://sites.google.com/view/d4rk7355608/more/apps/terms-of-service" ) @@ -71,7 +71,7 @@ fun PrivacySettingsComposable(activity: PrivacySettingsActivity) { PreferenceItem(title = stringResource(R.string.code_of_conduct), summary = stringResource(id = R.string.summary_preference_settings_code_of_conduct), onClick = { - Utils.openUrl( + IntentUtils.openUrl( context, "https://sites.google.com/view/d4rk7355608/more/code-of-conduct" ) @@ -79,21 +79,21 @@ fun PrivacySettingsComposable(activity: PrivacySettingsActivity) { PreferenceItem(title = stringResource(R.string.permissions), summary = stringResource(id = R.string.summary_preference_settings_permissions), onClick = { - Utils.openActivity( + IntentUtils.openActivity( context, PermissionsSettingsActivity::class.java ) }) PreferenceItem(title = stringResource(R.string.ads), summary = stringResource(id = R.string.summary_preference_settings_ads), onClick = { - Utils.openActivity( + IntentUtils.openActivity( context, AdsSettingsActivity::class.java ) }) PreferenceItem(title = stringResource(R.string.usage_and_diagnostics), summary = stringResource(id = R.string.summary_preference_settings_usage_and_diagnostics), onClick = { - Utils.openActivity( + IntentUtils.openActivity( context, UsageAndDiagnosticsActivity::class.java ) }) @@ -103,7 +103,7 @@ fun PrivacySettingsComposable(activity: PrivacySettingsActivity) { PreferenceItem(title = stringResource(R.string.legal_notices), summary = stringResource(id = R.string.summary_preference_settings_legal_notices), onClick = { - Utils.openUrl( + IntentUtils.openUrl( context, "https://sites.google.com/view/d4rk7355608/more/apps/legal-notices" ) @@ -111,7 +111,7 @@ fun PrivacySettingsComposable(activity: PrivacySettingsActivity) { PreferenceItem(title = stringResource(R.string.license), summary = stringResource(R.string.summary_preference_settings_license), onClick = { - Utils.openUrl(context, "https://www.gnu.org/licenses/gpl-3.0") + IntentUtils.openUrl(context, "https://www.gnu.org/licenses/gpl-3.0") }) } } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/ads/AdsSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/ads/AdsSettingsComposable.kt index 928f713..cf05af4 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/ads/AdsSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/ads/AdsSettingsComposable.kt @@ -35,9 +35,9 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.data.datastore.DataStore -import com.d4rk.cartcalculator.utils.PreferenceItem -import com.d4rk.cartcalculator.utils.SwitchCardComposable -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem +import com.d4rk.cartcalculator.utils.compose.components.SwitchCardComposable +import com.d4rk.cartcalculator.utils.IntentUtils import com.google.android.ump.ConsentRequestParameters import com.google.android.ump.UserMessagingPlatform import kotlinx.coroutines.Dispatchers @@ -126,7 +126,7 @@ fun AdsSettingsComposable(activity : AdsSettingsActivity) { ClickableText(text = annotatedString , onClick = { offset -> annotatedString.getStringAnnotations("URL" , offset , offset) .firstOrNull()?.let { annotation -> - Utils.openUrl(context , annotation.item) + IntentUtils.openUrl(context , annotation.item) } }) } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt index 6d1bfd7..310884d 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt @@ -18,8 +18,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import com.d4rk.cartcalculator.R -import com.d4rk.cartcalculator.utils.PreferenceCategoryItem -import com.d4rk.cartcalculator.utils.PreferenceItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceCategoryItem +import com.d4rk.cartcalculator.utils.compose.components.PreferenceItem @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt index 1e54d6f..d18bf65 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt @@ -35,8 +35,8 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.data.datastore.DataStore -import com.d4rk.cartcalculator.utils.SwitchCardComposable -import com.d4rk.cartcalculator.utils.Utils +import com.d4rk.cartcalculator.utils.compose.components.SwitchCardComposable +import com.d4rk.cartcalculator.utils.IntentUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -106,7 +106,7 @@ fun UsageAndDiagnosticsComposable(activity : UsageAndDiagnosticsActivity) { ClickableText(text = annotatedString , onClick = { offset -> annotatedString.getStringAnnotations("URL" , offset , offset) .firstOrNull()?.let { annotation -> - Utils.openUrl(context , annotation.item) + IntentUtils.openUrl(context , annotation.item) } }) } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupActivity.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupActivity.kt index 0152e83..2b45ea9 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupActivity.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupActivity.kt @@ -1,7 +1,5 @@ package com.d4rk.cartcalculator.ui.startup -import android.Manifest -import android.os.Build import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -15,30 +13,31 @@ import com.google.android.ump.ConsentForm import com.google.android.ump.ConsentInformation import com.google.android.ump.ConsentRequestParameters import com.google.android.ump.UserMessagingPlatform +import kotlinx.coroutines.flow.MutableStateFlow class StartupActivity : AppCompatActivity() { - private lateinit var consentInformation: ConsentInformation - private lateinit var consentForm: ConsentForm - override fun onCreate(savedInstanceState: Bundle?) { + private lateinit var consentInformation : ConsentInformation + private lateinit var consentForm : ConsentForm + val consentFormShown = MutableStateFlow(false) + override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { AppTheme { Surface( - modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + modifier = Modifier.fillMaxSize() , color = MaterialTheme.colorScheme.background ) { - StartupComposable() + StartupComposable(this@StartupActivity) } } } val params = ConsentRequestParameters.Builder().setTagForUnderAgeOfConsent(false).build() consentInformation = UserMessagingPlatform.getConsentInformation(this) - consentInformation.requestConsentInfoUpdate(this, params, { + consentInformation.requestConsentInfoUpdate(this , params , { if (consentInformation.isConsentFormAvailable) { loadForm() } - }, {}) - requestPermissions() + } , {}) } /** @@ -53,29 +52,14 @@ class StartupActivity : AppCompatActivity() { * @see com.google.ads.consent.ConsentInformation */ private fun loadForm() { - UserMessagingPlatform.loadConsentForm(this, { consentForm -> + UserMessagingPlatform.loadConsentForm(this , { consentForm -> this.consentForm = consentForm if (consentInformation.consentStatus == ConsentInformation.ConsentStatus.REQUIRED) { + consentFormShown.value = true consentForm.show(this) { loadForm() } } - }, {}) - } - - /** - * Handles the application's permission requirements. - * - * This function is responsible for checking and requesting the necessary permissions for the application. It takes into account the Android version to manage specific permission scenarios. - * For Android versions Tiramisu or later, it requests the POST_NOTIFICATIONS permission. - * - * @see android.Manifest.permission.POST_NOTIFICATIONS - * @see android.os.Build.VERSION.SDK_INT - * @see android.os.Build.VERSION_CODES.TIRAMISU - */ - private fun requestPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1) - } + } , {}) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupComposable.kt index 14eb35a..0b1ae94 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/startup/StartupComposable.kt @@ -1,5 +1,6 @@ package com.d4rk.cartcalculator.ui.startup +import android.app.Activity import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -12,6 +13,7 @@ import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme @@ -20,8 +22,12 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color.Companion.Gray import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -33,83 +39,100 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.MainActivity import com.d4rk.cartcalculator.R -import com.d4rk.cartcalculator.utils.Utils -import com.d4rk.cartcalculator.utils.bounceClick +import com.d4rk.cartcalculator.utils.IntentUtils +import com.d4rk.cartcalculator.utils.PermissionsUtils +import com.d4rk.cartcalculator.utils.compose.bounceClick @OptIn(ExperimentalMaterial3Api::class) @Composable -fun StartupComposable() { +fun StartupComposable(activity: StartupActivity) { val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) - Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) , topBar = { - LargeTopAppBar(title = { Text(stringResource(R.string.welcome)) } , - scrollBehavior = scrollBehavior + val fabEnabled = remember { mutableStateOf(false) } + LaunchedEffect(context) { + if (!PermissionsUtils.hasNotificationPermission(context)) { + PermissionsUtils.requestNotificationPermission(context as Activity) + } + activity.consentFormShown.collect { shown -> + fabEnabled.value = shown + } + } + + Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { + LargeTopAppBar( + title = { Text(stringResource(R.string.welcome)) }, + scrollBehavior = scrollBehavior ) }) { innerPadding -> Box( modifier = Modifier - .fillMaxSize() - .padding(24.dp) - .safeDrawingPadding() + .fillMaxSize() + .padding(24.dp) + .safeDrawingPadding() ) { LazyColumn( modifier = Modifier - .fillMaxSize() - .padding(innerPadding) , + .fillMaxSize() + .padding(innerPadding), ) { item { Image( - painter = painterResource(id = R.drawable.il_startup) , + painter = painterResource(id = R.drawable.il_startup), contentDescription = null ) Icon( - Icons.Outlined.Info , contentDescription = null + Icons.Outlined.Info, contentDescription = null ) } item { Text( - text = stringResource(R.string.summary_browse_terms_of_service_and_privacy_policy) , - modifier = Modifier.padding(top = 24.dp , bottom = 24.dp) + text = stringResource(R.string.summary_browse_terms_of_service_and_privacy_policy), + modifier = Modifier.padding(top = 24.dp, bottom = 24.dp) ) val annotatedString = buildAnnotatedString { withStyle( style = SpanStyle( - color = MaterialTheme.colorScheme.primary , + color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline ) ) { append(stringResource(R.string.browse_terms_of_service_and_privacy_policy)) } addStringAnnotation( - tag = "URL" , - annotation = "https://sites.google.com/view/d4rk7355608/more/apps/privacy-policy" , - start = 0 , + tag = "URL", + annotation = "https://sites.google.com/view/d4rk7355608/more/apps/privacy-policy", + start = 0, end = stringResource(R.string.browse_terms_of_service_and_privacy_policy).length ) } - ClickableText(text = annotatedString , onClick = { offset -> - annotatedString.getStringAnnotations("URL" , offset , offset).firstOrNull() - ?.let { annotation -> - Utils.openUrl(context , annotation.item) - } + ClickableText(text = annotatedString, onClick = { offset -> + annotatedString.getStringAnnotations("URL", offset, offset).firstOrNull() + ?.let { annotation -> + IntentUtils.openUrl(context, annotation.item) + } }) } } ExtendedFloatingActionButton(modifier = Modifier - .align(Alignment.BottomEnd) - .bounceClick() , - text = { Text(stringResource(R.string.agree)) } , - onClick = { - Utils.openActivity( - context , MainActivity::class.java - ) - } , - icon = { - Icon( - Icons.Outlined.CheckCircle , - contentDescription = null - ) - }) + .align(Alignment.BottomEnd) + .bounceClick(), + containerColor = if (fabEnabled.value) { + FloatingActionButtonDefaults.containerColor + } else { + Gray + }, + text = { Text(stringResource(R.string.agree)) }, + onClick = { + IntentUtils.openActivity( + context , MainActivity::class.java + ) + }, + icon = { + Icon( + Icons.Outlined.CheckCircle, + contentDescription = null + ) + }) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportActivity.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportActivity.kt index 0554cbb..28e752c 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportActivity.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportActivity.kt @@ -5,56 +5,42 @@ package com.d4rk.cartcalculator.ui.support import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Modifier import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.SkuDetails -import com.android.billingclient.api.SkuDetailsParams import com.d4rk.cartcalculator.ui.settings.display.theme.style.AppTheme class SupportActivity : AppCompatActivity() { + private val viewModel: SupportViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { AppTheme { Surface( - modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background ) { - SupportComposable(this@SupportActivity) + SupportComposable(viewModel, this@SupportActivity) } } } } fun initiatePurchase( - sku: String, skuDetailsMap: Map, billingClient: BillingClient + sku : String , skuDetailsMap : Map , billingClient : BillingClient ) { val skuDetails = skuDetailsMap[sku] if (skuDetails != null) { val flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build() - billingClient.launchBillingFlow(this, flowParams) - } - } - - fun querySkuDetails( - billingClient: BillingClient, skuDetailsMap: SnapshotStateMap - ) { - val skuList = - listOf("low_donation", "normal_donation", "high_donation", "extreme_donation") - val params = SkuDetailsParams.newBuilder().setSkusList(skuList) - .setType(BillingClient.SkuType.INAPP).build() - billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList -> - if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList != null) { - for (skuDetails in skuDetailsList) { - skuDetailsMap[skuDetails.sku] = skuDetails - } - } + billingClient.launchBillingFlow(this , flowParams) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportComposable.kt index e0aa508..d580322 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportComposable.kt @@ -30,10 +30,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -42,23 +39,18 @@ import androidx.compose.ui.unit.dp import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingResult -import com.android.billingclient.api.SkuDetails import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.ads.LargeBannerAdsComposable import com.d4rk.cartcalculator.data.datastore.DataStore -import com.d4rk.cartcalculator.utils.Utils -import com.d4rk.cartcalculator.utils.bounceClick -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import com.d4rk.cartcalculator.utils.IntentUtils +import com.d4rk.cartcalculator.utils.compose.bounceClick @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SupportComposable(activity: SupportActivity) { +fun SupportComposable(viewModel: SupportViewModel, activity: SupportActivity) { val context = LocalContext.current val dataStore = DataStore.getInstance(context) - val coroutineScope = rememberCoroutineScope() - val skuDetailsMap = remember { mutableStateMapOf() } - val billingClient = rememberBillingClient(context, coroutineScope, activity, skuDetailsMap) + val billingClient = rememberBillingClient(context, viewModel) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { LargeTopAppBar(title = { Text(stringResource(R.string.support_us)) }, navigationIcon = { @@ -69,8 +61,7 @@ fun SupportComposable(activity: SupportActivity) { Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null ) } - }, scrollBehavior = scrollBehavior - ) + }, scrollBehavior = scrollBehavior) }) { paddingValues -> Box( modifier = Modifier @@ -109,7 +100,9 @@ fun SupportComposable(activity: SupportActivity) { .bounceClick(), onClick = { activity.initiatePurchase( - "low_donation", skuDetailsMap, billingClient + "low_donation", + viewModel.skuDetails, + billingClient, ) }, ) { @@ -119,7 +112,7 @@ fun SupportComposable(activity: SupportActivity) { modifier = Modifier.size(ButtonDefaults.IconSize) ) Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(skuDetailsMap["low_donation"]?.price ?: "") + Text(viewModel.skuDetails["low_donation"]?.price ?: "") } } item { @@ -129,7 +122,9 @@ fun SupportComposable(activity: SupportActivity) { .bounceClick(), onClick = { activity.initiatePurchase( - "normal_donation", skuDetailsMap, billingClient + "normal_donation", + viewModel.skuDetails, + billingClient, ) }, ) { @@ -139,7 +134,7 @@ fun SupportComposable(activity: SupportActivity) { modifier = Modifier.size(ButtonDefaults.IconSize) ) Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(skuDetailsMap["normal_donation"]?.price ?: "") + Text(viewModel.skuDetails["normal_donation"]?.price ?: "") } } } @@ -156,7 +151,9 @@ fun SupportComposable(activity: SupportActivity) { .bounceClick(), onClick = { activity.initiatePurchase( - "high_donation", skuDetailsMap, billingClient + "high_donation", + viewModel.skuDetails, + billingClient, ) }, ) { @@ -166,7 +163,7 @@ fun SupportComposable(activity: SupportActivity) { modifier = Modifier.size(ButtonDefaults.IconSize) ) Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(skuDetailsMap["high_donation"]?.price ?: "") + Text(viewModel.skuDetails["high_donation"]?.price ?: "") } } item { @@ -177,7 +174,9 @@ fun SupportComposable(activity: SupportActivity) { .bounceClick(), onClick = { activity.initiatePurchase( - "extreme_donation", skuDetailsMap, billingClient + "extreme_donation", + viewModel.skuDetails, + billingClient, ) }, ) { @@ -187,7 +186,7 @@ fun SupportComposable(activity: SupportActivity) { modifier = Modifier.size(ButtonDefaults.IconSize) ) Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) - Text(skuDetailsMap["extreme_donation"]?.price ?: "") + Text(viewModel.skuDetails["extreme_donation"]?.price ?: "") } } } @@ -204,9 +203,8 @@ fun SupportComposable(activity: SupportActivity) { item { FilledTonalButton( onClick = { - Utils.openUrl( - context, - "https://direct-link.net/548212/agOqI7123501341" + IntentUtils.openUrl( + context, "https://direct-link.net/548212/agOqI7123501341" ) }, modifier = Modifier @@ -225,8 +223,7 @@ fun SupportComposable(activity: SupportActivity) { } item { LargeBannerAdsComposable( - modifier = Modifier.padding(bottom = 12.dp), - dataStore = dataStore + modifier = Modifier.padding(bottom = 12.dp), dataStore = dataStore ) } } @@ -237,20 +234,20 @@ fun SupportComposable(activity: SupportActivity) { @Composable fun rememberBillingClient( context: Context, - coroutineScope: CoroutineScope, - activity: SupportActivity, - skuDetailsMap: SnapshotStateMap + viewModel: SupportViewModel ): BillingClient { val billingClient = remember { - BillingClient.newBuilder(context).setListener { _, _ -> }.enablePendingPurchases().build() + BillingClient.newBuilder(context) + .setListener { _, _ -> } + .enablePendingPurchases() + .build() } + DisposableEffect(billingClient) { billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { - coroutineScope.launch { - activity.querySkuDetails(billingClient, skuDetailsMap) - } + viewModel.querySkuDetails(billingClient) } } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportViewModel.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportViewModel.kt new file mode 100644 index 0000000..3bf288b --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/support/SupportViewModel.kt @@ -0,0 +1,37 @@ +@file:Suppress("DEPRECATION") + +package com.d4rk.cartcalculator.ui.support + +import androidx.compose.runtime.mutableStateMapOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.SkuDetails +import com.android.billingclient.api.SkuDetailsParams +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class SupportViewModel : ViewModel() { + private val _skuDetails = mutableStateMapOf() + val skuDetails: Map = _skuDetails + + fun querySkuDetails(billingClient: BillingClient) { + viewModelScope.launch(Dispatchers.IO) { + val skuList = listOf( + "low_donation", "normal_donation", "high_donation", "extreme_donation" + ) + val params = SkuDetailsParams.newBuilder() + .setSkusList(skuList) + .setType(BillingClient.SkuType.INAPP) + .build() + + billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList -> + if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList != null) { + skuDetailsList.forEach { skuDetails -> + _skuDetails[skuDetails.sku] = skuDetails + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/Utils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt similarity index 99% rename from app/src/main/kotlin/com/d4rk/cartcalculator/utils/Utils.kt rename to app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt index 21a8bc0..df0c4fd 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/Utils.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt @@ -12,7 +12,7 @@ import com.d4rk.cartcalculator.R * This object provides functions to open a URL in the default browser, open an activity, and open the app's notification settings. * All operations are performed in the context of an Android application. */ -object Utils { +object IntentUtils { /** * Opens a specified URL in the default browser. diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/PermissionsUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/PermissionsUtils.kt new file mode 100644 index 0000000..ab1db10 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/PermissionsUtils.kt @@ -0,0 +1,48 @@ +package com.d4rk.cartcalculator.utils + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.d4rk.cartcalculator.constants.permissions.PermissionsConstants + +/** + * Utility class for handling runtime permissions. + */ +object PermissionsUtils { + + /** + * Checks if the app has permission to post notifications. + * + * @param context The application context. + * @return True if the permission is granted, false otherwise. + */ + fun hasNotificationPermission(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else { + true + } + } + + /** + * Requests the notification permission. + * + * @param activity The Activity instance required to request the permission. + */ + fun requestNotificationPermission(activity: Activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityCompat.requestPermissions( + activity, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + PermissionsConstants.REQUEST_CODE_NOTIFICATION_PERMISSION + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/compose/AnimationUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/compose/AnimationUtils.kt new file mode 100644 index 0000000..1caab1f --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/compose/AnimationUtils.kt @@ -0,0 +1,47 @@ +package com.d4rk.cartcalculator.utils.compose + +import android.annotation.SuppressLint +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.waitForUpOrCancellation +import androidx.compose.foundation.interaction.MutableInteractionSource +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.composed +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import com.d4rk.cartcalculator.data.model.ui.button.ButtonState + +@SuppressLint("ReturnFromAwaitPointerEventScope") +@Composable +fun Modifier.bounceClick() = composed { + var buttonState by remember { mutableStateOf(ButtonState.Idle) } + val scale by animateFloatAsState( + if (buttonState == ButtonState.Pressed) 0.95f else 1f , label = "" + ) + this + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clickable(interactionSource = remember { MutableInteractionSource() } , + indication = null , + onClick = { }) + .pointerInput(buttonState) { + awaitPointerEventScope { + buttonState = if (buttonState == ButtonState.Pressed) { + waitForUpOrCancellation() + ButtonState.Idle + } + else { + awaitFirstDown(false) + ButtonState.Pressed + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/ComposablesUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/compose/components/SettingsComposablesUtils.kt similarity index 87% rename from app/src/main/kotlin/com/d4rk/cartcalculator/utils/ComposablesUtils.kt rename to app/src/main/kotlin/com/d4rk/cartcalculator/utils/compose/components/SettingsComposablesUtils.kt index 6fd6bee..fdb2e46 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/ComposablesUtils.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/compose/components/SettingsComposablesUtils.kt @@ -1,11 +1,6 @@ -package com.d4rk.cartcalculator.utils +package com.d4rk.cartcalculator.utils.compose.components -import android.annotation.SuppressLint -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.awaitFirstDown -import androidx.compose.foundation.gestures.waitForUpOrCancellation -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -28,17 +23,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -268,34 +256,4 @@ fun SwitchPreferenceItemWithDivider( ) } -} - -enum class ButtonState { Pressed, Idle } - -@SuppressLint("ReturnFromAwaitPointerEventScope") -@Composable -fun Modifier.bounceClick() = composed { - var buttonState by remember { mutableStateOf(ButtonState.Idle) } - val scale by animateFloatAsState( - if (buttonState == ButtonState.Pressed) 0.95f else 1f, label = "" - ) - this - .graphicsLayer { - scaleX = scale - scaleY = scale - } - .clickable(interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { }) - .pointerInput(buttonState) { - awaitPointerEventScope { - buttonState = if (buttonState == ButtonState.Pressed) { - waitForUpOrCancellation() - ButtonState.Idle - } else { - awaitFirstDown(false) - ButtonState.Pressed - } - } - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/drawable/ImageUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/drawable/ImageUtils.kt new file mode 100644 index 0000000..eb5589d --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/drawable/ImageUtils.kt @@ -0,0 +1,24 @@ +package com.d4rk.cartcalculator.utils.drawable + +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable + +fun Drawable.toBitmapDrawable(resources: Resources = Resources.getSystem()): BitmapDrawable { + return when (this) { + is BitmapDrawable -> this + is AdaptiveIconDrawable -> { + val bitmap = + Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + setBounds(0, 0, canvas.width, canvas.height) + draw(canvas) + BitmapDrawable(resources, bitmap) + } + + else -> throw IllegalArgumentException("Unsupported drawable type: ${this::class.java.name}") + } +} \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..3ee8640 --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,189 @@ + + Gerencie e calcule seus itens do carrinho de forma eficiente com o Cart Calculator. ?? + Uma nova atualização está disponível! + Uma nova versão do aplicativo está disponível. Clique para atualizar! + Já faz um tempo! + Vamos gerenciar seus itens do carrinho de forma eficiente! + + Bem-vindo + Navegue pelos Termos de serviço e Política de privacidade + Leia e concorde com nossos Termos de serviço e Política de privacidade para continuar. + Concordar + + Nenhum carrinho disponível + Adicionar novo carrinho + Nome do carrinho + Carrinho de compras + Estamos comprometidos em criar uma experiência de compra personalizada para todos. Para facilitar o acompanhamento de suas seleções e melhorar sua jornada de compras, considere dar um nome ao seu carrinho antes de adicionar itens. + Criado em %1$s + Diminuir quantidade + Aumentar quantidade + Excluir carrinho + Tem certeza de que deseja excluir este carrinho? + Excluir este carrinho removerá permanentemente todos os itens de \"%s\" e não poderá ser desfeito. + Carrinho excluído com sucesso! + Erro ao excluir o carrinho. Tente novamente. + Fechar? + Tem certeza de que deseja sair? + Erro de rede ocorreu ao verificar atualizações + Ocorreu um erro ao verificar atualizações + + Seu carrinho de compras está vazio + Nome do item + Digite o nome do item + Preço do item + Digite o preço do item + Quantidade + Digite a quantidade + Para garantir uma experiência de checkout perfeita, forneça detalhes sobre os itens que você está adicionando ao seu carrinho + Total + Excluir item do carrinho + Tem certeza de que deseja excluir este item? + Você está prestes a excluir \"%s\" do seu carrinho. + + Configurações + + Exibição + Personalize a aparência do seu aplicativo + + Aparência + Tema escuro + Ligará automaticamente pelo sistema + Nunca ligará automaticamente + Modo AMOLED + Seguir modo do sistema + Modo claro + Modo escuro + O tema escuro usa um fundo escuro para ajudar a manter a bateria viva por mais tempo + + Cores dinâmicas + Aplique cores dos papéis de parede ao tema do aplicativo + + Idioma + Altera o idioma usado no aplicativo + Selecione seu idioma preferido + Personalize sua experiência com seu idioma preferido. Quaisquer alterações de idioma que você fizer entram em vigor imediatamente, garantindo uma experiência perfeita no idioma escolhido + + Configurações do carrinho + Gerencie como seu carrinho se comporta + + Moeda + Defina sua moeda preferida para exibir preços + Selecione sua moeda preferida + Esta seleção de moeda será utilizada em toda a plataforma para exibir os preços dos produtos, processar transações e fornecer uma experiência de compra mais personalizada + + Notificações + Gerencie as notificações do aplicativo + + Notificações de uso do aplicativo + Notificações de atualização + + Avançado + Explore configurações mais avançadas + Relato de erros + Relato de bugs + Envie relatórios de bugs e solicitações de recursos para a página de problemas do repositório GitHub do aplicativo + + Segurança e privacidade + Gerencie suas configurações de privacidade + Privacidade + Política de privacidade + Veja a política que rege como lidamos com seus dados + Termos de serviço + Revise os termos com os quais você concorda ao usar nosso serviço + Código de conduta + Entenda as regras e diretrizes para o comportamento dentro de nosso serviço + + Permissões + Gerencie as permissões concedidas ao nosso serviço + Normal + ID do anúncio [AD_ID] + Permite que o aplicativo recupere e use o identificador de publicidade associado ao dispositivo do usuário, fornecendo anúncios personalizados, medindo a eficácia dos anúncios e exibindo anúncios em dispositivos Android 13 ou posterior. + Internet [INTERNET] + Permite que o aplicativo estabeleça uma conexão com a Internet para enviar relatórios de erros ou verificar atualizações. + Publicar notificações [POST_NOTIFICATIONS] + Permite que o aplicativo exiba notificações nos dispositivos com Android 13 ou posterior. + Tempo de execução + Acessar estado da rede [ACCESS_NETWORK_STATE] + Permite que o aplicativo verifique a conectividade de rede e recupere informações sobre Wi-Fi, incluindo status habilitado e nomes de dispositivos Wi-Fi conectados. + Acessar política de notificação [ACCESS_NOTIFICATION_POLICY] + Permite que o aplicativo acesse e modifique a política de notificação do dispositivo, controlando como e quando as notificações são exibidas para o usuário e fornecendo recursos de gerenciamento de notificações personalizados. + Faturamento [BILLING] + Permite que o aplicativo use a biblioteca de faturamento do Google Play para lidar com compras dentro do aplicativo e doações + Verificar licença [CHECK_LICENSE] + Permite que o aplicativo verifique sua conformidade com o contrato de licença e aplique os termos de licença para proteger a propriedade intellectual. + Serviço em primeiro plano [FOREGROUND_SERVICE] + Permite que o aplicativo crie e use serviços que são executados em primeiro plano, dando a eels prioridade sobre outros processos em segundo plano e melhorando o desempenho e a confiabilidade. + + Anúncios + Controle como usamos suas informações para mostrar anúncios relevantes + Exibir anúncios + Anúncios personalizados + Escolha como personalizamos anúncios para você com base em seus interesses + Veja anúncios que são relevantes para você. Gerencie as informações usadas para exibir anúncios personalizados com base em sua atividade no aplicativo. Você sempre pode desativar a personalização aqui.\n + + Uso e diagnóstica + Compartilhe dados para ajudar a melhorar o Cart Calculator + Ajude a melhorar sua experiência enviando automaticamente dados de diagnóstico, dispositivo e uso do aplicativo para nós. Isso ajudará a melhorar o desempenho do aplicativo, a estabilidade e outras melhorias. Alguns dados agregados também ajudarão nossos aplicativos.\n\nEstas são informações gerais sobre seu dispositivo e como você usa nossos aplicativos (como nível da bateria, atividade do sistema e do aplicativo e erros). Os dados serão usados para melhorar nossos aplicativos\n\nDesativar este recurso não afeta a capacidade do seu dispositivo de enviar as informações necessárias para serviços essenciais, como atualizações de aplicativos e segurança.\n + + Legal + Avisos legais + Veja informações legais sobre nosso serviço + Licença + + Sobre + Saiba mais sobre o Cart Calculator + + Informações do aplicativo + Versão de compilação do aplicativo + Detalhes da licença para software de código aberto + Informações do dispositivo + Compilação do aplicativo: Release\n%1$s\n%2$s\n%3$s\n%4$s\n%5$s + Fabricante: + Modelo do dispositivo: + Versão do Android: + Nível da API: + + Ajuda e feedback + Ajuda + FAQ + Ver na Google Play Store + Informações da versão + Versão %1$s + Programa beta + O que é Cart Calculator? + O Cart Calculator é um gerenciador de carrinho de compras simples, genérico e gratuito e calculadora de total a pagar que substitui a calculadora normal do dispositivo quando você está no supermercado ou no mercado. + Como funciona o Cart Calculator? + O Cart Calculator permite que você gerencie seu carrinho de compras adicionando produtos e aumentando ou diminuindo suas quantidades. A qualquer memento, o Cart Calculator exibe o total a pagar dos produtos em seu carrinho com base em seus preços e quantidades. + Por que devo usar o Cart Calculator? + O Cart Calculator ajuda você a estar sempre ciente de quanto está gastando, mesmo antes de chegar ao caixa. Isso é especialmente útil quando você precisa fazer compras com orçamento limitado. + Posso usar o Cart Calculator fora de supermercados ou mercados? + Sim! Como o Cart Calculator é incrivelmente simples e genérico, você também pode usá-lo em pubs, restaurantes, cinemas, etc. + Como adiciono um item ao meu carrinho usando o Cart Calculator? + Na tela inicial, pressione o botão \"Adicionar item\" na barra de ações para adicionar um novo produto ao seu carrinho. Você informa seu nome e preço e depois clica no botão \"Adicionar\"." + Como aumento ou diminuo a quantidade de um item no meu carrinho usando o Cart Calculator? + Basta encontrar o item na lista e pressionar o botão \"+ (mais)\" para aumentar sua quantidade ou o botão \"- (menos)\" para diminuir sua quantidade." + Como removo um item do meu carrinho usando o Cart Calculator? + Para remover um produto do seu carrinho, basta reduzir sua quantidade a zero ou clicar longamente nele na lista e depois selecionar o botão \"Excluir item\"." + O Cart Calculator é um software livre? + Sim! O Cart Calculator é orgulhosamente um software livre distribuído sob a GNU General Public License versão 3. + Como posso contribuir para o Cart Calculator? + A melhor maneira de contribuir para o Cart Calculator é por meio de seu repositório GitHub. A partir daí, você pode fazer um fork e trabalhar em suas contribuições e, posteriormente, enviá-las de volta por meio de uma solicitação de pull. Você também pode usar o rastreador de problemas para relatar bugs, propor novos recursos ou melhorias ou obter ideias de como você pode ajudar. + Feedback + + Atualizações + + Compartilhar + + Apoie-nos + Suporte pago + Não importa quanto você doe, você nos ajudará a manter nosso aplicativo funcionando e melhorar nossos recursos. Agradecemos sua generosidade e gentileza! + Suporte não pago + Anúncio na Web + + O aplicativo foi atualizado com sucesso + O processo de atualização encontrou um problema + Tentar novamente + Confira este aplicativo incrível que estou usando! Ele tem alguns recursos realmente legais que você pode achar interessantes. Você pode baixá-lo na Play Store em: %1$s + Saiba mais + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6a13645..3b3e577 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -40,6 +40,7 @@ @string/spanish @string/turkish @string/ukrainian + @string/portuguese_brazil bg @@ -57,5 +58,6 @@ es tr uk + pt-BR \ No newline at end of file diff --git a/app/src/main/res/values/untranslatable_strings.xml b/app/src/main/res/values/untranslatable_strings.xml index 86ca723..7e5d449 100644 --- a/app/src/main/res/values/untranslatable_strings.xml +++ b/app/src/main/res/values/untranslatable_strings.xml @@ -16,6 +16,7 @@ Español Türkçe Українська + Português (Brasil) General Public License-3.0 Feedback for Dear developer, diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index db9a3cd..f3cdc79 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 597e12a..71e48bd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,6 +15,7 @@ pluginManagement { } } } + @Suppress("UnstableApiUsage") dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { @@ -25,5 +26,6 @@ pluginManagement { } } } + rootProject.name = "Cart Calculator" include(":app") \ No newline at end of file