diff --git a/CHANGELOG.md b/CHANGELOG.md index be88ba5..22952f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# Version 1.1.1 + +- **New**: Improved cart screen layout to better distinguish between in-cart and pickup items. +- **Minor**: Enhanced snack bar animations for a more engaging user experience. +- **Minor**: Added support for Traditional Chinese. +- **Minor**: Performed quality improvements and updated project dependencies. +- **Minor**: Implemented a new color palette to enhance the visual consistency and aesthetics of + static elements within the user interface. +- **Fix**: Fixed a bug that caused the incorrect cart to be shown when quickly accessing a freshly + created one. + # Version 1.1.0 - **New**: Integrated haptic feedback for swipe gestures, enhancing the tactile response and user diff --git a/EULA.md b/EULA.md new file mode 100644 index 0000000..c95bc4a --- /dev/null +++ b/EULA.md @@ -0,0 +1,75 @@ +# End-User License Agreement (EULA) + +This End-User License Agreement ("EULA") governs your use of any and all software applications +developed and distributed by D4rK. By installing, +copying, using, or redistributing the Software, you acknowledge that you have read, understood, and +agree to be bound by the terms of this EULA. + +## License Grant + +The Developer grants you a non-exclusive, revocable, worldwide, royalty-free license to use the +Software for personal, non-commercial purposes, subject to the limitations set forth in this EULA. +Unless otherwise expressly stated in writing by the Developer, you may not: + +* Sublicense, distribute, or transfer the Software to any third party. +* Modify, adapt, translate, reverse engineer, decompile, or disassemble the Software. +* Remove, alter, or obscure any copyright, trademark, or other proprietary notices contained in the + Software. +* Use the Software in any way that violates any applicable laws or regulations. +* Use the Software to engage in any illegal, harmful, or offensive activity. +* Use the Software to infringe the intellectual property rights of any third party. + +## Intellectual Property + +The Software and all related intellectual property rights, including but not limited to copyrights, +trademarks, and trade secrets, are owned by the Developer. This EULA does not grant you any +ownership rights in the Software. + +## Disclaimer of Warranties + +THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND +NON-INFRINGEMENT. IN NO EVENT SHALL THE DEVELOPER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE DEVELOPER DOES NOT +WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, UNINTERRUPTED, OR FREE OF VIRUSES OR OTHER HARMFUL +COMPONENTS. + +## Limitation of Liability + +IN NO EVENT SHALL THE DEVELOPER BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR +EXEMPLARY DAMAGES, INCLUDING BUT NOT LIMITED TO DAMAGES FOR LOSS OF PROFITS, DATA, OR USE, ARISING +OUT OF OR IN CONNECTION WITH THE SOFTWARE, EVEN IF THE DEVELOPER HAS BEEN ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +## Term and Termination + +This EULA is effective upon your acceptance and shall continue until terminated. Your rights under +this EULA will terminate automatically without notice from the Developer if you breach any term of +this EULA. Upon termination, you must cease all use of the Software and destroy all copies in your +possession. + +## Governing Law + +This EULA shall be governed by and construed in accordance with the laws of Romania. Any dispute +arising out of or in connection with this EULA shall be subject to the exclusive jurisdiction of the +competent courts of Bucharest, Romania. + +## Contact Information + +If you have any questions or concerns regarding this EULA, please contact the Developer +at d4rk7355608@gmail.com. + +## Open Source Components + +This Software may incorporate or utilize certain open-source components. These components are +governed by their respective licenses, which can be +found 'About' or 'Legal' section of the app. Your use of these components is +subject to the terms of their respective licenses. + +## Changes to this EULA + +The Developer reserves the right to modify this EULA at any time. Any changes will be effective upon +posting the revised EULA at https://github.com/D4rK7355608/com.d4rk.cartcalculator/EULA.md. Your +continued use of the Software after the effective date of any changes constitutes your acceptance of +the revised EULA. \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ef5fcd7..c2901f0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,10 +2,10 @@ plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.jetbrainsKotlinAndroid) alias(libs.plugins.googlePlayServices) - alias(libs.plugins.googleOssServices) alias(libs.plugins.googleFirebase) alias(libs.plugins.compose.compiler) alias(libs.plugins.devToolsKsp) + alias(libs.plugins.about.libraries) } android { @@ -15,7 +15,7 @@ android { applicationId = "com.d4rk.cartcalculator" minSdk = 23 targetSdk = 35 - versionCode = 66 + versionCode = 67 versionName = "1.1.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" resourceConfigurations += listOf( @@ -44,11 +44,21 @@ android { } } + signingConfigs { + create("release") { + keyAlias = "key0" + keyPassword = "GoogleD4rK10" + storeFile = file("C:/Users/mihai.condrea/StudioProjects/com.d4rk.cartcalculator/app/src/main/play/keys/com.d4rk.cartcalculator.jks") + storePassword = "GoogleD4rK10" + } + } + buildTypes { release { isMinifyEnabled = true isShrinkResources = true isDebuggable = false + signingConfig = signingConfigs.getByName("release") } debug { isDebuggable = true @@ -104,7 +114,6 @@ dependencies { implementation(dependencyNotation = libs.play.services.ads) implementation(dependencyNotation = libs.billing) implementation(dependencyNotation = libs.material) - implementation(dependencyNotation = libs.play.services.oss.licenses) implementation(dependencyNotation = libs.review.ktx) implementation(dependencyNotation = libs.app.update.ktx) implementation(dependencyNotation = libs.volley) @@ -148,6 +157,10 @@ dependencies { // Image Compression implementation(dependencyNotation = libs.coil.compose) + // About + implementation(dependencyNotation = libs.aboutlibraries) + implementation(dependencyNotation = libs.core) + // Test testImplementation(dependencyNotation = libs.junit) androidTestImplementation(dependencyNotation = libs.androidx.junit) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/data/core/AppCoreManager.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/data/core/AppCoreManager.kt index 3e2e7d1..fc38c5b 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/data/core/AppCoreManager.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/data/core/AppCoreManager.kt @@ -2,6 +2,7 @@ package com.d4rk.cartcalculator.data.core +import android.annotation.SuppressLint import android.app.Activity import android.app.Application import android.os.Bundle @@ -34,6 +35,9 @@ class AppCoreManager : MultiDexApplication(), Application.ActivityLifecycleCallb companion object { lateinit var database : AppDatabase + @SuppressLint("StaticFieldLeak") + lateinit var instance: AppCoreManager + private set } private var currentStage = AppInitializationStage.DATA_STORE @@ -41,6 +45,7 @@ class AppCoreManager : MultiDexApplication(), Application.ActivityLifecycleCallb override fun onCreate() { super.onCreate() + instance = this registerActivityLifecycleCallbacks(this) ProcessLifecycleOwner.get().lifecycle.addObserver(observer = this) database = Room.databaseBuilder(this , AppDatabase::class.java , "Cart Calculator") diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/Snackbar.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/Snackbar.kt new file mode 100644 index 0000000..12d4bf7 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/Snackbar.kt @@ -0,0 +1,34 @@ +package com.d4rk.cartcalculator.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun Snackbar( + message : String , showSnackbar : Boolean , onDismiss : () -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(showSnackbar) { + if (showSnackbar) { + snackbarHostState.showSnackbar(message , duration = SnackbarDuration.Short) + onDismiss() + } + } + + Box( + modifier = Modifier.fillMaxSize() + ) { + SnackbarHost( + hostState = snackbarHostState , modifier = Modifier.align(Alignment.BottomCenter) + ) + } +} diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt index bce4fe4..87f4fb7 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt @@ -54,7 +54,7 @@ import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.ui.components.animations.bounceClick import com.d4rk.cartcalculator.ui.components.dialogs.VersionInfoAlertDialog import com.d4rk.cartcalculator.utils.IntentUtils -import com.google.android.gms.oss.licenses.OssLicensesMenuActivity +import com.d4rk.cartcalculator.utils.rememberHtmlData import com.google.android.play.core.review.ReviewInfo @OptIn(ExperimentalMaterial3Api::class) @@ -67,6 +67,11 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) { val view : View = LocalView.current val showDialog : MutableState = remember { mutableStateOf(value = false) } val reviewInfo : ReviewInfo? = viewModel.reviewInfo.value + + val htmlData = rememberHtmlData() + val changelogHtmlString = htmlData.value.first + val eulaHtmlString = htmlData.value.second + if (reviewInfo != null) { LaunchedEffect(key1 = reviewInfo) { viewModel.requestReviewFlow() @@ -145,11 +150,13 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) { ) }) DropdownMenuItem(modifier = Modifier.bounceClick() , - text = { Text(text = stringResource(com.google.android.gms.oss.licenses.R.string.oss_license_title)) } , + text = { Text(text = stringResource(R.string.oss_license_title)) } , onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) - IntentUtils.openActivity( - context , OssLicensesMenuActivity::class.java + IntentUtils.openLicensesScreen( + context = context , + eulaHtmlString = eulaHtmlString , + changelogHtmlString = changelogHtmlString ) }) } @@ -169,8 +176,7 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) { view.playSoundEffect(SoundEffectConstants.CLICK) viewModel.reviewInfo.value?.let { safeReviewInfo -> viewModel.launchReviewFlow( - activity , - safeReviewInfo + activity , safeReviewInfo ) } } , @@ -180,19 +186,18 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) { contentDescription = null ) } , - modifier = Modifier - .bounceClick()) + modifier = Modifier.bounceClick()) } , ) { paddingValues -> Box( modifier = Modifier - .padding(start = 16.dp, end = 16.dp) + .padding(start = 16.dp , end = 16.dp) .fillMaxSize() .safeDrawingPadding() ) { Column(modifier = Modifier.padding(paddingValues)) { Text( - text = stringResource(id = R.string.faq), + text = stringResource(id = R.string.faq) , modifier = Modifier.padding(bottom = 24.dp) ) Card(modifier = Modifier.fillMaxWidth()) { diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt index f99d4c7..4c5e709 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt @@ -1,23 +1,16 @@ package com.d4rk.cartcalculator.ui.screens.settings.about -import android.content.ClipData -import android.content.ClipboardManager import android.content.Context import android.os.Build import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState 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 import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -25,30 +18,24 @@ import com.d4rk.cartcalculator.BuildConfig import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.ui.components.PreferenceCategoryItem import com.d4rk.cartcalculator.ui.components.PreferenceItem +import com.d4rk.cartcalculator.ui.components.Snackbar import com.d4rk.cartcalculator.ui.components.navigation.TopAppBarScaffoldWithBackButton +import com.d4rk.cartcalculator.utils.ClipboardUtil import com.d4rk.cartcalculator.utils.IntentUtils -import com.google.android.gms.oss.licenses.OssLicensesMenuActivity +import com.d4rk.cartcalculator.utils.rememberHtmlData @Composable -fun AboutSettingsComposable(activity: AboutSettingsActivity) { - val context: Context = LocalContext.current - val clipboardManager: ClipboardManager = - context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - var showSnackbar: Boolean by remember { mutableStateOf(value = false) } - val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } - val snackbarMessage: String = stringResource(id = R.string.snack_device_info_copied) +fun AboutSettingsComposable(activity : AboutSettingsActivity) { + val context : Context = LocalContext.current - LaunchedEffect(showSnackbar) { - if (showSnackbar) { - snackbarHostState.showSnackbar( - message = snackbarMessage, duration = SnackbarDuration.Short - ) - showSnackbar = false - } - } + var showSnackbar : Boolean by remember { mutableStateOf(value = false) } + + val htmlData = rememberHtmlData() + val changelogHtmlString = htmlData.value.first + val eulaHtmlString = htmlData.value.second TopAppBarScaffoldWithBackButton( - title = stringResource(id = R.string.about), + title = stringResource(id = R.string.about) , onBackClicked = { activity.finish() }) { paddingValues -> Box(modifier = Modifier.padding(paddingValues)) { LazyColumn( @@ -59,57 +46,57 @@ fun AboutSettingsComposable(activity: AboutSettingsActivity) { } item(key = "app_name") { PreferenceItem( - title = stringResource(id = R.string.app_name), - summary = stringResource(id = R.string.copyright), + title = stringResource(id = R.string.app_name) , + summary = stringResource(id = R.string.copyright) , ) } item(key = "app_build_version") { PreferenceItem( - title = stringResource(id = R.string.app_build_version), + title = stringResource(id = R.string.app_build_version) , summary = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" ) } item(key = "oss_licenses") { - PreferenceItem( - title = stringResource(id = com.google.android.gms.oss.licenses.R.string.oss_license_title), - summary = stringResource(id = R.string.summary_preference_settings_oss), - onClick = { - IntentUtils.openActivity( - context, OssLicensesMenuActivity::class.java - ) - } - ) + PreferenceItem(title = stringResource(R.string.oss_license_title) , + summary = stringResource(id = R.string.summary_preference_settings_oss) , + onClick = { + IntentUtils.openLicensesScreen( + context = context , + eulaHtmlString = eulaHtmlString , + changelogHtmlString = changelogHtmlString + ) + }) } item(key = "device_info_category") { PreferenceCategoryItem(title = stringResource(id = R.string.device_info)) } item(key = "device_info") { - val version: String = stringResource( - id = R.string.app_build, - "${stringResource(id = R.string.manufacturer)} ${Build.MANUFACTURER}", - "${stringResource(id = R.string.device_model)} ${Build.MODEL}", - "${stringResource(id = R.string.android_version)} ${Build.VERSION.RELEASE}", - "${stringResource(id = R.string.api_level)} ${Build.VERSION.SDK_INT}", - "${stringResource(id = R.string.arch)} ${Build.SUPPORTED_ABIS.joinToString()}", - if (BuildConfig.DEBUG) stringResource(id = R.string.debug) else stringResource(id = R.string.release) + val version : String = stringResource( + id = R.string.app_build , + "${stringResource(id = R.string.manufacturer)} ${Build.MANUFACTURER}" , + "${stringResource(id = R.string.device_model)} ${Build.MODEL}" , + "${stringResource(id = R.string.android_version)} ${Build.VERSION.RELEASE}" , + "${stringResource(id = R.string.api_level)} ${Build.VERSION.SDK_INT}" , + "${stringResource(id = R.string.arch)} ${Build.SUPPORTED_ABIS.joinToString()}" , + if (BuildConfig.DEBUG) stringResource(id = R.string.debug) + else stringResource(id = R.string.release) ) - PreferenceItem( - title = stringResource(id = R.string.device_info), - summary = version, - onClick = { - val clip: ClipData = ClipData.newPlainText("text", version) - clipboardManager.setPrimaryClip(clip) - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { - showSnackbar = true - } - } - ) + PreferenceItem(title = stringResource(id = R.string.device_info) , + summary = version , + onClick = { + ClipboardUtil.copyTextToClipboard(context = context , + label = "Device Info" , + text = version , + onShowSnackbar = { + showSnackbar = true + }) + }) } } - SnackbarHost( - hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter) - ) + Snackbar(message = stringResource(id = R.string.snack_device_info_copied) , + showSnackbar = showSnackbar , + onDismiss = { showSnackbar = false }) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/ClipboardUtil.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/ClipboardUtil.kt new file mode 100644 index 0000000..8d79156 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/ClipboardUtil.kt @@ -0,0 +1,20 @@ +package com.d4rk.cartcalculator.utils + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build + +object ClipboardUtil { + fun copyTextToClipboard( + context : Context , label : String , text : String , onShowSnackbar : () -> Unit = {} + ) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(label , text) + clipboard.setPrimaryClip(clip) + + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + onShowSnackbar() + } + } +} diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt index df0c4fd..9f3ca21 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/IntentUtils.kt @@ -3,8 +3,11 @@ package com.d4rk.cartcalculator.utils import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import android.provider.Settings +import com.d4rk.cartcalculator.BuildConfig import com.d4rk.cartcalculator.R +import com.mikepenz.aboutlibraries.LibsBuilder /** * A utility object for performing common operations such as opening URLs, activities, and app notification settings. @@ -23,8 +26,8 @@ object IntentUtils { * @param context The Android context in which the URL should be opened. * @param url The URL to open. */ - fun openUrl(context: Context, url: String) { - Intent(Intent.ACTION_VIEW, Uri.parse(url)).let { intent -> + fun openUrl(context : Context , url : String) { + Intent(Intent.ACTION_VIEW , Uri.parse(url)).let { intent -> intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } @@ -38,8 +41,8 @@ object IntentUtils { * @param context The Android context in which the activity should be opened. * @param activityClass The class of the activity to open. */ - fun openActivity(context: Context, activityClass: Class<*>) { - Intent(context, activityClass).let { intent -> + fun openActivity(context : Context , activityClass : Class<*>) { + Intent(context , activityClass).let { intent -> intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } @@ -53,11 +56,19 @@ object IntentUtils { * * @param context The Android context in which the app's notification settings should be opened. */ - fun openAppNotificationSettings(context: Context) { - val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + fun openAppNotificationSettings(context : Context) { + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE , context.packageName) + } } + else { + Intent().apply { + action = "android.settings.APPLICATION_DETAILS_SETTINGS" + data = Uri.fromParts("package" , context.packageName , null) + } + } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } @@ -69,19 +80,19 @@ object IntentUtils { * * @param context The Android context in which the share sheet should be opened. */ - fun shareApp(context: Context) { + fun shareApp(context : Context) { val shareMessage = context.getString( - R.string.summary_share_message, + R.string.summary_share_message , "https://play.google.com/store/apps/details?id=${context.packageName}" ) val shareIntent = Intent().apply { action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, shareMessage) + putExtra(Intent.EXTRA_TEXT , shareMessage) type = "text/plain" } context.startActivity( Intent.createChooser( - shareIntent, context.resources.getText(R.string.send_email_using) + shareIntent , context.resources.getText(R.string.send_email_using) ) ) } @@ -96,7 +107,7 @@ object IntentUtils { * * @param context The Android context used to access resources and start the activity. */ - fun sendEmailToDeveloper(context: Context) { + fun sendEmailToDeveloper(context : Context) { val developerEmail = "d4rk7355608@gmail.com" val appName = context.getString(R.string.app_name) val subject = context.getString(R.string.feedback_for) + appName @@ -104,14 +115,50 @@ object IntentUtils { val emailIntent = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("mailto:$developerEmail") - putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, body) + putExtra(Intent.EXTRA_SUBJECT , subject) + putExtra(Intent.EXTRA_TEXT , body) } context.startActivity( Intent.createChooser( - emailIntent, context.resources.getText(R.string.send_email_using) + emailIntent , context.resources.getText(R.string.send_email_using) ) ) } + + fun openLicensesScreen( + context : Context , + eulaHtmlString : String? , + changelogHtmlString : String? + ) { + LibsBuilder().withActivityTitle( + activityTitle = context.getString(R.string.oss_license_title) + ).withEdgeToEdge(asEdgeToEdge = true).withShowLoadingProgress(showLoadingProgress = true) + .withSearchEnabled(searchEnabled = true).withAboutIconShown(aboutShowIcon = true) + .withAboutAppName( + aboutAppName = context.getString(R.string.app_name) + ).withVersionShown(showVersion = true) + .withAboutVersionString(aboutVersionString = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") + + .withLicenseShown(showLicense = true).withAboutVersionShown(aboutShowVersion = true) + .withAboutVersionShownName(aboutShowVersion = true) + .withAboutVersionShownCode(aboutShowVersion = true) + + .withAboutSpecial1( + aboutAppSpecial1 = context.getString( + R.string.eula_title + ) + ).withAboutSpecial1Description( + aboutAppSpecial1Description = eulaHtmlString + ?: context.getString(R.string.loading_eula) + ).withAboutSpecial2( + aboutAppSpecial2 = context.getString(R.string.changelog) + ).withAboutSpecial2Description( + aboutAppSpecial2Description = changelogHtmlString + ?: context.getString(R.string.loading_changelog) + ) + + .withAboutDescription(context.getString(R.string.app_short_description)) + .activity(context) + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt new file mode 100644 index 0000000..83777af --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt @@ -0,0 +1,99 @@ +package com.d4rk.cartcalculator.utils + +import android.app.Application +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import com.d4rk.cartcalculator.BuildConfig +import com.d4rk.cartcalculator.R +import com.d4rk.cartcalculator.utils.helpers.getStringResource +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.commonmark.parser.Parser +import org.commonmark.renderer.html.HtmlRenderer +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL + +object OpenSourceLicensesUtils { + val packageName = BuildConfig.APPLICATION_ID + + private suspend fun getChangelogMarkdown(): String { + return withContext(Dispatchers.IO) { + val url = URL("https://raw.githubusercontent.com/$packageName}/refs/heads/jetpack_compose_rework/CHANGELOG.md") + (url.openConnection() as? HttpURLConnection)?.let { connection -> + try { + connection.requestMethod = "GET" + if (connection.responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader(InputStreamReader(connection.inputStream)).use { reader -> + return@withContext reader.readText() + } + } else { + getStringResource(R.string.error_loading_changelog) + } + } finally { + connection.disconnect() + } + } ?: getStringResource(R.string.error_loading_changelog) + } + } + + private suspend fun getEulaMarkdown(): String { + return withContext(Dispatchers.IO) { + val url = URL("https://raw.githubusercontent.com/$packageName}/refs/heads/jetpack_compose_rework/EULA.md") + (url.openConnection() as? HttpURLConnection)?.let { connection -> + try { + connection.requestMethod = "GET" + if (connection.responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader(InputStreamReader(connection.inputStream)).use { reader -> + return@withContext reader.readText() + } + } else { + getStringResource(R.string.error_loading_eula) + } + } finally { + connection.disconnect() + } + } ?: getStringResource(R.string.error_loading_eula) + } + } + + private fun extractLatestVersionChangelog(markdown: String): String { + val currentVersion = BuildConfig.VERSION_NAME + val regex = Regex(pattern = "(# Version\\s+$currentVersion:\\s*[\\s\\S]*?)(?=# Version|$)") + val match = regex.find(markdown) + return match?.groups?.get(1)?.value?.trim() ?: "No changelog available for version $currentVersion" + } + + private fun convertMarkdownToHtml(markdown: String): String { + val parser = Parser.builder().build() + val renderer = HtmlRenderer.builder().build() + val document = parser.parse(markdown) + return renderer.render(document) + } + + suspend fun loadHtmlData(): Pair { + val changelogMarkdown = getChangelogMarkdown() + val extractedChangelog = extractLatestVersionChangelog(changelogMarkdown) + val changelogHtml = convertMarkdownToHtml(extractedChangelog) + + val eulaMarkdown = getEulaMarkdown() + val eulaHtml = convertMarkdownToHtml(eulaMarkdown) + + return changelogHtml to eulaHtml + } +} + +@Composable +fun rememberHtmlData(): State> { + val htmlDataState = remember { mutableStateOf>(value = null to null) } + + LaunchedEffect(Unit) { + htmlDataState.value = OpenSourceLicensesUtils.loadHtmlData() + } + + return htmlDataState +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/helpers/StringsHelper.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/helpers/StringsHelper.kt new file mode 100644 index 0000000..2bcacb3 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/helpers/StringsHelper.kt @@ -0,0 +1,8 @@ +package com.d4rk.cartcalculator.utils.helpers + +import androidx.annotation.StringRes +import com.d4rk.cartcalculator.data.core.AppCoreManager + +fun getStringResource(@StringRes id: Int): String { + return AppCoreManager.instance.getString(id) +} \ 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 6402e59..f0972f8 100644 --- a/app/src/main/res/values/untranslatable_strings.xml +++ b/app/src/main/res/values/untranslatable_strings.xml @@ -2,6 +2,7 @@ Cart Calculator Shopping Cart Calculator for Android + Open source licenses Arch: Български English @@ -26,4 +27,11 @@ Dear developer, Send mail using: Copyright ©2023-2024, D4rK + + EULA + CHANGELOG + Loading EULA + Error loading EULA + Loading changelog + Error loading changelog \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 699133e..60ed030 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,6 @@ plugins { alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.googlePlayServices) apply false alias(libs.plugins.googleFirebase) apply false - alias(libs.plugins.googleOssServices) apply false alias(libs.plugins.devToolsKsp) apply false + alias(libs.plugins.about.libraries) apply true } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ae685ee..eccec5e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,9 @@ agp = "8.7.2" appcompat = "1.7.0" appUpdateKtx = "2.1.0" -composeBom = "2024.10.01" +composeBom = "2024.11.00" +aboutlibraries = "11.2.3" +core = "4.6.2" coreKtx = "1.15.0" billing = "7.1.1" datastoreCore = "1.1.1" @@ -15,30 +17,30 @@ volley = "1.2.1" workRuntimeKtx = "2.10.0" coreSplashscreen = "1.0.1" coilCompose = "2.7.0" -firebaseBom = "33.5.1" +firebaseBom = "33.6.0" kotlin = "2.0.10" junit = "4.13.2" activityCompose = "1.9.3" junitVersion = "1.2.1" -navigationCompose = "2.8.3" +navigationCompose = "2.8.4" espressoCore = "3.6.1" composeUi = "1.7.5" google-services = "4.4.2" -google-oss-services = "0.10.6" google-firebase-crashlytics = "3.0.2" google-devtools-ksp = "2.0.10-1.0.24" roomKtx = "2.6.1" playServicesAds = "23.5.0" -playServicesOssLicenses = "17.1.0" reviewKtx = "2.0.2" [libraries] +aboutlibraries = { module = "com.mikepenz:aboutlibraries", version.ref = "aboutlibraries" } androidx-animation-core = { module = "androidx.compose.animation:animation-core" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } +core = { module = "io.noties.markwon:core", version.ref = "core" } datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastoreCore" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-core", version.ref = "datastoreCore" } androidx-foundation = { module = "androidx.compose.foundation:foundation" } @@ -73,7 +75,6 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } material = { module = "com.google.android.material:material", version.ref = "material" } play-services-ads = { module = "com.google.android.gms:play-services-ads", version.ref = "playServicesAds" } -play-services-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version.ref = "playServicesOssLicenses" } review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "reviewKtx" } ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "composeUi" } ui-tooling = { module = "androidx.compose.ui:ui-tooling" } @@ -85,7 +86,7 @@ androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } googlePlayServices = { id = "com.google.gms.google-services", version.ref = "google-services" } -googleOssServices = { id = "com.google.android.gms.oss-licenses-plugin", version.ref = "google-oss-services" } googleFirebase = { id = "com.google.firebase.crashlytics", version.ref = "google-firebase-crashlytics" } devToolsKsp = { id = "com.google.devtools.ksp", version.ref = "google-devtools-ksp" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } \ No newline at end of file +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a17539b..9a80724 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Oct 26 18:05:05 EEST 2024 +#Thu Nov 21 08:19:48 EET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 71e48bd..6757d15 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,13 +7,6 @@ pluginManagement { setUrl("https://jitpack.io") } } - resolutionStrategy { - eachPlugin { - if (requested.id.id == "com.google.android.gms.oss-licenses-plugin") { - useModule("com.google.android.gms:oss-licenses-plugin:${requested.version}") - } - } - } } @Suppress("UnstableApiUsage") dependencyResolutionManagement {