Refactor foss/market APIs

This commit is contained in:
T8RIN
2025-03-20 03:36:02 +03:00
parent 4992c0454d
commit 879463dfd1
9 changed files with 191 additions and 204 deletions

View File

@ -30,15 +30,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.findActivity
object ReviewHandler {
internal object ReviewHandlerImpl : ReviewHandler {
private val Context.dataStore by preferencesDataStore("saves_count")
private val SAVES_COUNT = intPreferencesKey("SAVES_COUNT")
private val NOT_SHOW_AGAIN = booleanPreferencesKey("NOT_SHOW_AGAIN")
private val _showNotShowAgainButton = mutableStateOf(false)
val showNotShowAgainButton: Boolean by _showNotShowAgainButton
override val showNotShowAgainButton: Boolean by _showNotShowAgainButton
fun showReview(
override fun showReview(
context: Context,
onComplete: () -> Unit = {}
) {
@ -68,7 +68,7 @@ object ReviewHandler {
}
}
fun notShowReviewAgain(context: Context) {
override fun notShowReviewAgain(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
context.dataStore.edit {
it[NOT_SHOW_AGAIN] = true

View File

@ -0,0 +1,35 @@
/*
* ImageToolbox is an image editor for android
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* You should have received a copy of the Apache License
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package ru.tech.imageresizershrinker.core.ui.widget.sheets
import androidx.compose.runtime.Composable
@Composable
fun UpdateSheet(
changelog: String,
tag: String,
visible: Boolean,
onDismiss: () -> Unit
) {
DefaultUpdateSheet(
changelog = changelog,
tag = tag,
visible = visible,
onDismiss = onDismiss
)
}

View File

@ -26,7 +26,7 @@ import ru.tech.imageresizershrinker.core.domain.saving.model.SaveResult
import ru.tech.imageresizershrinker.core.domain.utils.ListUtils.firstOfType
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.requestStoragePermission
import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler.showReview
import ru.tech.imageresizershrinker.core.ui.utils.helper.ReviewHandler.Companion.showReview
import ru.tech.imageresizershrinker.core.ui.utils.provider.LocalEssentials
import ru.tech.imageresizershrinker.core.ui.widget.other.ToastDuration

View File

@ -0,0 +1,35 @@
/*
* ImageToolbox is an image editor for android
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* You should have received a copy of the Apache License
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package ru.tech.imageresizershrinker.core.ui.utils.helper
import android.content.Context
interface ReviewHandler {
val showNotShowAgainButton: Boolean
fun showReview(
context: Context,
onComplete: () -> Unit = {}
)
fun notShowReviewAgain(context: Context)
companion object : ReviewHandler by ReviewHandlerImpl
}

View File

@ -1,6 +1,6 @@
/*
* ImageToolbox is an image editor for android
* Copyright (c) 2024 T8RIN (Malik Mukhametzyanov)
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,10 +15,8 @@
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package ru.tech.imageresizershrinker.core.ui.widget
package ru.tech.imageresizershrinker.core.ui.widget.sheets
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -36,10 +34,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import ru.tech.imageresizershrinker.core.domain.APP_RELEASES
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedButton
@ -50,14 +47,12 @@ import ru.tech.imageresizershrinker.core.ui.widget.text.HtmlText
import ru.tech.imageresizershrinker.core.ui.widget.text.TitleItem
@Composable
fun UpdateSheet(
internal fun DefaultUpdateSheet(
changelog: String,
tag: String,
visible: Boolean,
onDismiss: () -> Unit
) {
val context = LocalContext.current
EnhancedModalBottomSheet(
visible = visible,
onDismiss = {
@ -90,27 +85,31 @@ fun UpdateSheet(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(Modifier.verticalScroll(rememberScrollState())) {
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
val linkHandler = LocalUriHandler.current
HtmlText(
html = changelog.trimIndent(),
modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 24.dp)
) { uri ->
context.startActivity(Intent(Intent.ACTION_VIEW, uri.toUri()))
}
modifier = Modifier.padding(
start = 24.dp,
end = 24.dp,
top = 24.dp
),
onHyperlinkClick = linkHandler::openUri
)
}
}
}
}
},
confirmButton = {
val linkHandler = LocalUriHandler.current
EnhancedButton(
onClick = {
context.startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse("$APP_RELEASES/tag/${tag}")
)
)
linkHandler.openUri("$APP_RELEASES/tag/${tag}")
}
) {
AutoSizeText(stringResource(id = R.string.update))

View File

@ -21,13 +21,13 @@ import android.app.Activity
import android.content.Context
import com.google.android.play.core.review.ReviewManagerFactory
object ReviewHandler {
internal object ReviewHandlerImpl : ReviewHandler {
val showNotShowAgainButton: Boolean = false
override val showNotShowAgainButton: Boolean = false
fun showReview(
override fun showReview(
context: Context,
onComplete: () -> Unit = {}
onComplete: () -> Unit
) {
runCatching {
val reviewManager = ReviewManagerFactory.create(context)
@ -46,6 +46,6 @@ object ReviewHandler {
}
}
fun notShowReviewAgain(context: Context) = Unit
override fun notShowReviewAgain(context: Context) = Unit
}

View File

@ -1,173 +0,0 @@
/*
* ImageToolbox is an image editor for android
* Copyright (c) 2024 T8RIN (Malik Mukhametzyanov)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* You should have received a copy of the Apache License
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package ru.tech.imageresizershrinker.core.ui.widget
import android.app.Activity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.FileDownloadOff
import androidx.compose.material.icons.rounded.NewReleases
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.UpdateAvailability
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.domain.APP_RELEASES
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedButton
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedModalBottomSheet
import ru.tech.imageresizershrinker.core.ui.widget.enhanced.EnhancedModalSheetDragHandle
import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState
import ru.tech.imageresizershrinker.core.ui.widget.text.AutoSizeText
import ru.tech.imageresizershrinker.core.ui.widget.text.HtmlText
import ru.tech.imageresizershrinker.core.ui.widget.text.TitleItem
@Composable
fun UpdateSheet(
changelog: String,
tag: String,
visible: Boolean,
onDismiss: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val toastHostState = LocalToastHostState.current
if (context.isInstalledFromPlayStore()) {
LaunchedEffect(visible) {
if (visible) {
runCatching {
val appUpdateManager = AppUpdateManagerFactory.create(context)
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
appUpdateManager.startUpdateFlow(
appUpdateInfo,
context as Activity,
AppUpdateOptions.defaultOptions(AppUpdateType.IMMEDIATE)
)
} else {
scope.launch {
toastHostState.showToast(
icon = Icons.Rounded.FileDownloadOff,
message = context.getString(R.string.no_updates)
)
}
}
}
}.onFailure {
scope.launch {
toastHostState.showToast(
icon = Icons.Rounded.FileDownloadOff,
message = context.getString(R.string.no_updates)
)
}
}
}
}
} else {
EnhancedModalBottomSheet(
visible = visible,
onDismiss = {
if (!it) onDismiss()
},
title = {},
dragHandle = {
EnhancedModalSheetDragHandle {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxWidth()
) {
CompositionLocalProvider(
LocalContentColor.provides(MaterialTheme.colorScheme.onSurface),
LocalTextStyle.provides(MaterialTheme.typography.bodyLarge)
) {
TitleItem(
text = stringResource(R.string.new_version, tag),
icon = Icons.Rounded.NewReleases
)
}
}
}
},
sheetContent = {
ProvideTextStyle(value = MaterialTheme.typography.bodyMedium) {
Box {
Column(
modifier = Modifier.align(Alignment.TopCenter),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
) {
val linkHandler = LocalUriHandler.current
HtmlText(
html = changelog.trimIndent(),
modifier = Modifier.padding(
start = 24.dp,
end = 24.dp,
top = 24.dp
),
onHyperlinkClick = linkHandler::openUri
)
}
}
}
}
},
confirmButton = {
val linkHandler = LocalUriHandler.current
EnhancedButton(
onClick = {
linkHandler.openUri("$APP_RELEASES/tag/${tag}")
}
) {
AutoSizeText(stringResource(id = R.string.update))
}
}
)
}
}

View File

@ -0,0 +1,91 @@
/*
* ImageToolbox is an image editor for android
* Copyright (c) 2025 T8RIN (Malik Mukhametzyanov)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* You should have received a copy of the Apache License
* along with this program. If not, see <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package ru.tech.imageresizershrinker.core.ui.widget.sheets
import android.app.Activity
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.FileDownloadOff
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.UpdateAvailability
import kotlinx.coroutines.launch
import ru.tech.imageresizershrinker.core.resources.R
import ru.tech.imageresizershrinker.core.ui.utils.helper.ContextUtils.isInstalledFromPlayStore
import ru.tech.imageresizershrinker.core.ui.widget.other.LocalToastHostState
@Composable
fun UpdateSheet(
changelog: String,
tag: String,
visible: Boolean,
onDismiss: () -> Unit
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val toastHostState = LocalToastHostState.current
if (context.isInstalledFromPlayStore()) {
LaunchedEffect(visible) {
if (visible) {
runCatching {
val appUpdateManager = AppUpdateManagerFactory.create(context)
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
appUpdateManager.startUpdateFlow(
appUpdateInfo,
context as Activity,
AppUpdateOptions.defaultOptions(AppUpdateType.IMMEDIATE)
)
} else {
scope.launch {
toastHostState.showToast(
icon = Icons.Rounded.FileDownloadOff,
message = context.getString(R.string.no_updates)
)
}
}
}
}.onFailure {
scope.launch {
toastHostState.showToast(
icon = Icons.Rounded.FileDownloadOff,
message = context.getString(R.string.no_updates)
)
}
}
}
}
} else {
DefaultUpdateSheet(
changelog = changelog,
tag = tag,
visible = visible,
onDismiss = onDismiss
)
}
}