diff --git a/app/build.gradle.kts b/app/build.gradle.kts index be02827..4e2dae5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,7 +5,6 @@ plugins { alias(libs.plugins.googlePlayServices) alias(libs.plugins.googleOssServices) alias(libs.plugins.googleFirebase) - alias(libs.plugins.googleDaggerHilt) alias(libs.plugins.compose.compiler) alias(libs.plugins.devToolsKsp) } @@ -144,9 +143,6 @@ dependencies { ksp(libs.androidx.room.compiler) implementation(libs.androidx.room.ktx) implementation(libs.androidx.room.runtime) - ksp(libs.hilt.android.compiler) - implementation(libs.hilt.android) - implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Kotlin implementation(libs.kotlinx.coroutines.android) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 184d8cb..7f98d19 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + @@ -139,7 +140,7 @@ + android:value="ca-app-pub-5294151573817700~8003610653" /> { - 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 { @@ -147,7 +147,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/englishwithlidia/plus/constants/ads/AdsConstants.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/constants/ads/AdsConstants.kt index 8215b22..ee98491 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/constants/ads/AdsConstants.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/constants/ads/AdsConstants.kt @@ -8,13 +8,13 @@ object AdsConstants { get() = if (BuildConfig.DEBUG) { "ca-app-pub-3940256099942544/6300978111" } else { - "ca-app-pub-5294151573817700/8040893463" + "ca-app-pub-5294151573817700/8479403125" } val APP_OPEN_UNIT_ID: String get() = if (BuildConfig.DEBUG) { "ca-app-pub-3940256099942544/9257395921" } else { - "ca-app-pub-5294151573817700/9208287867" + "ca-app-pub-5294151573817700/2885662643" } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/constants/permissions/PermissionsConstants.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/constants/permissions/PermissionsConstants.kt new file mode 100644 index 0000000..94a2462 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/constants/permissions/PermissionsConstants.kt @@ -0,0 +1,5 @@ +package com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/data/model/ui/button/ButtonState.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/data/model/ui/button/ButtonState.kt new file mode 100644 index 0000000..e9b43f9 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/data/model/ui/button/ButtonState.kt @@ -0,0 +1,3 @@ +package com.d4rk.englishwithlidia.plus.data.model.ui.button + +enum class ButtonState { Pressed , Idle } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/dialogs/VersionInfoDialogComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/dialogs/VersionInfoDialogComposable.kt index 10ca790..8f05030 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/dialogs/VersionInfoDialogComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/dialogs/VersionInfoDialogComposable.kt @@ -1,11 +1,5 @@ package com.d4rk.englishwithlidia.plus.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.englishwithlidia.plus.BuildConfig import com.d4rk.englishwithlidia.plus.R +import com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/help/HelpActivity.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpActivity.kt index 6c20c50..2863a92 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpActivity.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpActivity.kt @@ -3,56 +3,27 @@ package com.d4rk.englishwithlidia.plus.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.englishwithlidia.plus.ui.settings.display.theme.style.AppTheme -import com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/help/HelpComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpComposable.kt index 3a6ae3f..b019d17 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.ui.dialogs.VersionInfoDialog -import com.d4rk.englishwithlidia.plus.utils.Utils -import com.d4rk.englishwithlidia.plus.utils.bounceClick +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/help/HelpViewModel.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpViewModel.kt new file mode 100644 index 0000000..5ce0899 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/help/HelpViewModel.kt @@ -0,0 +1,35 @@ +package com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/home/HomeComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/home/HomeComposable.kt index 3af4d6a..1d1aa1b 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/home/HomeComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/home/HomeComposable.kt @@ -1,7 +1,6 @@ package com.d4rk.englishwithlidia.plus.ui.home import android.content.Intent -import com.d4rk.englishwithlidia.plus.utils.drawable.homeBanner import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -45,8 +44,9 @@ import com.d4rk.englishwithlidia.plus.ads.BannerAdsComposable import com.d4rk.englishwithlidia.plus.data.datastore.DataStore import com.d4rk.englishwithlidia.plus.ui.home.repository.LessonRepository import com.d4rk.englishwithlidia.plus.ui.lessons.LessonsActivity -import com.d4rk.englishwithlidia.plus.utils.Utils -import com.d4rk.englishwithlidia.plus.utils.bounceClick +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.bounceClick +import com.d4rk.englishwithlidia.plus.utils.drawable.homeBanner @Composable fun HomeComposable() { @@ -78,7 +78,7 @@ fun HomeComposable() { .padding(start = 24.dp, end = 24.dp), ) { OutlinedButton(onClick = { - Utils.openUrl(context, "https://sites.google.com/view/englishwithlidia") + IntentUtils.openUrl(context, "https://sites.google.com/view/englishwithlidia") }, modifier = Modifier .weight(1f) .bounceClick()) { @@ -95,7 +95,7 @@ fun HomeComposable() { OutlinedButton( onClick = { - Utils.openUrl(context, "https://www.facebook.com/lidia.melinte") + IntentUtils.openUrl(context, "https://www.facebook.com/lidia.melinte") }, modifier = Modifier .weight(1f) diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsComposable.kt index 666e27d..9126488 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsComposable.kt @@ -1,6 +1,8 @@ package com.d4rk.englishwithlidia.plus.ui.lessons import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -46,7 +48,7 @@ import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.rememberLottieComposition import com.d4rk.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.data.model.ui.lessons.UiLessonsAsset -import com.d4rk.englishwithlidia.plus.utils.bounceClick +import com.d4rk.englishwithlidia.plus.utils.compose.bounceClick @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -92,7 +94,7 @@ fun LessonsComposable(viewModel: LessonsViewModel) { fun LessonContent( modifier: Modifier = Modifier, lessonDetails: UiLessonsAsset, - viewModel: LessonsViewModel + viewModel: LessonsViewModel, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.anim_plant)) val context = LocalContext.current @@ -149,8 +151,13 @@ fun AudioCardView( onSeekChange: (Float) -> Unit, sliderPosition: Float, playbackDuration: Float, - isPlaying: Boolean + isPlaying: Boolean, ) { + val cornerRadius = animateFloatAsState( + targetValue = if (isPlaying) 16f else 28f, + animationSpec = tween(durationMillis = 200), label = "" + ).value + OutlinedCard( modifier = Modifier .fillMaxWidth() @@ -172,7 +179,8 @@ fun AudioCardView( onClick = onPlayClick, modifier = Modifier .weight(1f) - .bounceClick() + .bounceClick(), + shape = RoundedCornerShape(cornerRadius.dp) ) { Icon( imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow, @@ -183,13 +191,15 @@ fun AudioCardView( Spacer(modifier = Modifier.width(16.dp)) Slider( - value = playbackDuration, - onValueChange = { - + value = sliderPosition, + onValueChange = { newValue -> + onSeekChange(newValue) }, colors = SliderDefaults.colors(), valueRange = 0f..playbackDuration, - modifier = Modifier.fillMaxWidth().weight(4f) + modifier = Modifier + .fillMaxWidth() + .weight(4f) ) } } diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsViewModel.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsViewModel.kt index 8205647..b4463b2 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/lessons/LessonsViewModel.kt @@ -3,7 +3,6 @@ package com.d4rk.englishwithlidia.plus.ui.lessons import android.app.Application import android.content.Context import android.net.Uri -import androidx.annotation.OptIn import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -11,24 +10,15 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import androidx.media3.common.MediaItem import androidx.media3.common.Player -import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import com.d4rk.englishwithlidia.plus.data.model.ui.lessons.UiLessonsAsset -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class LessonsViewModel @OptIn(UnstableApi::class) -@Inject constructor( - application: Application, - lessonDetails: UiLessonsAsset?, -) : AndroidViewModel(application) { + +class LessonsViewModel(application: Application, lessonDetails: UiLessonsAsset?) : AndroidViewModel(application) { private val _isPlaying = MutableStateFlow(false) val isPlaying: StateFlow = _isPlaying.asStateFlow() diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/SettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/SettingsComposable.kt index 69819be..e1da44c 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/SettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/SettingsComposable.kt @@ -28,8 +28,8 @@ import com.d4rk.englishwithlidia.plus.ui.settings.about.AboutSettingsActivity import com.d4rk.englishwithlidia.plus.ui.settings.advanced.AdvancedSettingsActivity import com.d4rk.englishwithlidia.plus.ui.settings.display.DisplaySettingsActivity import com.d4rk.englishwithlidia.plus.ui.settings.privacy.PrivacySettingsActivity -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -55,7 +55,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 { @@ -63,7 +63,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 { @@ -71,7 +71,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 ) }) @@ -81,7 +81,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 { @@ -89,7 +89,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/englishwithlidia/plus/ui/settings/about/AboutSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/about/AboutSettingsComposable.kt index 6edcf72..93716dc 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/about/AboutSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/about/AboutSettingsComposable.kt @@ -24,9 +24,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import com.d4rk.englishwithlidia.plus.BuildConfig import com.d4rk.englishwithlidia.plus.R -import com.d4rk.englishwithlidia.plus.utils.PreferenceCategoryItem -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceCategoryItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem 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/englishwithlidia/plus/ui/settings/advanced/AdvancedSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/advanced/AdvancedSettingsComposable.kt index 9277bbe..c3e6a0d 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/advanced/AdvancedSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R -import com.d4rk.englishwithlidia.plus.utils.PreferenceCategoryItem -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceCategoryItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem @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/englishwithlidia/plus/ui/settings/display/DisplaySettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/display/DisplaySettingsComposable.kt index dff799c..c77fa60 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/display/DisplaySettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/display/DisplaySettingsComposable.kt @@ -34,11 +34,11 @@ import com.d4rk.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.data.datastore.DataStore import com.d4rk.englishwithlidia.plus.ui.dialogs.LanguageDialog import com.d4rk.englishwithlidia.plus.ui.settings.display.theme.ThemeSettingsActivity -import com.d4rk.englishwithlidia.plus.utils.PreferenceCategoryItem -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem -import com.d4rk.englishwithlidia.plus.utils.SwitchPreferenceItem -import com.d4rk.englishwithlidia.plus.utils.SwitchPreferenceItemWithDivider -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceCategoryItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.SwitchPreferenceItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.SwitchPreferenceItemWithDivider 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/englishwithlidia/plus/ui/settings/display/theme/ThemeSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/display/theme/ThemeSettingsComposable.kt index 1cfc54a..d883471 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/display/theme/ThemeSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.data.datastore.DataStore -import com.d4rk.englishwithlidia.plus.utils.SwitchCardComposable +import com.d4rk.englishwithlidia.plus.utils.compose.components.SwitchCardComposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/PrivacySettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/PrivacySettingsComposable.kt index 1e28be8..53cab25 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/PrivacySettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/PrivacySettingsComposable.kt @@ -22,9 +22,9 @@ import com.d4rk.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.ui.settings.privacy.ads.AdsSettingsActivity import com.d4rk.englishwithlidia.plus.ui.settings.privacy.permissions.PermissionsSettingsActivity import com.d4rk.englishwithlidia.plus.ui.settings.privacy.usage.UsageAndDiagnosticsActivity -import com.d4rk.englishwithlidia.plus.utils.PreferenceCategoryItem -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceCategoryItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem @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/englishwithlidia/plus/ui/settings/privacy/ads/AdsSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/ads/AdsSettingsComposable.kt index 32315c9..a4b1192 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/ads/AdsSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.data.datastore.DataStore -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem -import com.d4rk.englishwithlidia.plus.utils.SwitchCardComposable -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.SwitchCardComposable 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/englishwithlidia/plus/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt index 9646b0f..c4f7edc 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/permissions/PermissionsSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R -import com.d4rk.englishwithlidia.plus.utils.PreferenceCategoryItem -import com.d4rk.englishwithlidia.plus.utils.PreferenceItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceCategoryItem +import com.d4rk.englishwithlidia.plus.utils.compose.components.PreferenceItem @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt index 4b00d8c..62e7fc4 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/settings/privacy/usage/UsageAndDiagnosticsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.data.datastore.DataStore -import com.d4rk.englishwithlidia.plus.utils.SwitchCardComposable -import com.d4rk.englishwithlidia.plus.utils.Utils +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.compose.components.SwitchCardComposable 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/englishwithlidia/plus/ui/startup/StartupActivity.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/startup/StartupActivity.kt index 790a1cd..8b79562 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/startup/StartupActivity.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/startup/StartupActivity.kt @@ -1,7 +1,5 @@ package com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/startup/StartupComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/startup/StartupComposable.kt index e0e91c5..46adc7b 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/startup/StartupComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/startup/StartupComposable.kt @@ -1,5 +1,6 @@ package com.d4rk.englishwithlidia.plus.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.englishwithlidia.plus.MainActivity import com.d4rk.englishwithlidia.plus.R -import com.d4rk.englishwithlidia.plus.utils.Utils -import com.d4rk.englishwithlidia.plus.utils.bounceClick +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.utils.PermissionsUtils +import com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/support/SupportActivity.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportActivity.kt index c5b5d9b..adf050f 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportActivity.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportActivity.kt @@ -5,56 +5,42 @@ package com.d4rk.englishwithlidia.plus.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.englishwithlidia.plus.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/englishwithlidia/plus/ui/support/SupportComposable.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportComposable.kt index ba3c41f..b93f0a9 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportComposable.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/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.englishwithlidia.plus.R import com.d4rk.englishwithlidia.plus.ads.LargeBannerAdsComposable import com.d4rk.englishwithlidia.plus.data.datastore.DataStore -import com.d4rk.englishwithlidia.plus.utils.Utils -import com.d4rk.englishwithlidia.plus.utils.bounceClick -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import com.d4rk.englishwithlidia.plus.utils.IntentUtils +import com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/ui/support/SupportViewModel.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportViewModel.kt new file mode 100644 index 0000000..b6b665b --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/ui/support/SupportViewModel.kt @@ -0,0 +1,37 @@ +@file:Suppress("DEPRECATION") + +package com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/utils/Utils.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/IntentUtils.kt similarity index 99% rename from app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/Utils.kt rename to app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/IntentUtils.kt index 9613d2d..8b749ee 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/Utils.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/IntentUtils.kt @@ -12,7 +12,7 @@ import com.d4rk.englishwithlidia.plus.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/englishwithlidia/plus/utils/PermissionsUtils.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/PermissionsUtils.kt new file mode 100644 index 0000000..b8a0049 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/PermissionsUtils.kt @@ -0,0 +1,48 @@ +package com.d4rk.englishwithlidia.plus.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.englishwithlidia.plus.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/englishwithlidia/plus/utils/compose/AnimationUtils.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/compose/AnimationUtils.kt new file mode 100644 index 0000000..ae96c35 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/compose/AnimationUtils.kt @@ -0,0 +1,47 @@ +package com.d4rk.englishwithlidia.plus.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.englishwithlidia.plus.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/englishwithlidia/plus/utils/ComposablesUtils.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/compose/components/SettingsComposablesUtils.kt similarity index 70% rename from app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/ComposablesUtils.kt rename to app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/compose/components/SettingsComposablesUtils.kt index 28d1599..1fe4089 100644 --- a/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/ComposablesUtils.kt +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/compose/components/SettingsComposablesUtils.kt @@ -1,11 +1,6 @@ -package com.d4rk.englishwithlidia.plus.utils +package com.d4rk.englishwithlidia.plus.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 @@ -54,34 +42,35 @@ import androidx.compose.ui.unit.dp */ @Composable fun SwitchCardComposable( - title: String, switchState: State, onSwitchToggled: (Boolean) -> Unit + title : String , switchState : State , onSwitchToggled : (Boolean) -> Unit ) { Card(modifier = Modifier .fillMaxWidth() .padding(24.dp) .clip(RoundedCornerShape(28.dp)) .clickable { - onSwitchToggled(!switchState.value) + onSwitchToggled(! switchState.value) }) { Row( modifier = Modifier .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, + .padding(16.dp) , + horizontalArrangement = Arrangement.SpaceBetween , verticalAlignment = Alignment.CenterVertically ) { Text(text = title) - Switch(checked = switchState.value, - onCheckedChange = onSwitchToggled, + Switch(checked = switchState.value , + onCheckedChange = onSwitchToggled , thumbContent = if (switchState.value) { { Icon( - Icons.Filled.Check, - contentDescription = null, - modifier = Modifier.size(SwitchDefaults.IconSize), + Icons.Filled.Check , + contentDescription = null , + modifier = Modifier.size(SwitchDefaults.IconSize) , ) } - } else { + } + else { null }) } @@ -97,13 +86,13 @@ fun SwitchCardComposable( */ @Composable fun PreferenceCategoryItem( - title: String + title : String ) { Text( - text = title, - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold), - modifier = Modifier.padding(start = 16.dp, top = 16.dp) + text = title , + color = MaterialTheme.colorScheme.primary , + style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.SemiBold) , + modifier = Modifier.padding(start = 16.dp , top = 16.dp) ) } @@ -127,9 +116,9 @@ fun PreferenceItem( ) { Row( modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(16.dp)) - .clickable(enabled = enabled , onClick = onClick) , + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .clickable(enabled = enabled , onClick = onClick) , verticalAlignment = Alignment.CenterVertically ) { icon?.let { @@ -172,22 +161,22 @@ fun PreferenceItem( */ @Composable fun SwitchPreferenceItem( - icon: ImageVector? = null, - title: String, - summary: String? = null, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit + icon : ImageVector? = null , + title : String , + summary : String? = null , + checked : Boolean , + onCheckedChange : (Boolean) -> Unit ) { Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(16.dp)) - .clickable(onClick = { onCheckedChange(!checked) }), + .clickable(onClick = { onCheckedChange(! checked) }) , verticalAlignment = Alignment.CenterVertically ) { icon?.let { Spacer(modifier = Modifier.width(16.dp)) - Icon(it, contentDescription = null) + Icon(it , contentDescription = null) Spacer(modifier = Modifier.width(16.dp)) } Column( @@ -195,14 +184,14 @@ fun SwitchPreferenceItem( .padding(16.dp) .weight(1f) ) { - Text(text = title, style = MaterialTheme.typography.titleLarge) + Text(text = title , style = MaterialTheme.typography.titleLarge) summary?.let { - Text(text = it, style = MaterialTheme.typography.bodyMedium) + Text(text = it , style = MaterialTheme.typography.bodyMedium) } } Switch( - checked = checked, - onCheckedChange = onCheckedChange, + checked = checked , + onCheckedChange = onCheckedChange , modifier = Modifier.padding(16.dp) ) } @@ -225,23 +214,23 @@ fun SwitchPreferenceItem( */ @Composable fun SwitchPreferenceItemWithDivider( - icon: ImageVector? = null, - title: String, - summary: String, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - onClick: () -> Unit, - onSwitchClick: (Boolean) -> Unit + icon : ImageVector? = null , + title : String , + summary : String , + checked : Boolean , + onCheckedChange : (Boolean) -> Unit , + onClick : () -> Unit , + onSwitchClick : (Boolean) -> Unit ) { Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(16.dp)) - .clickable(onClick = onClick), verticalAlignment = Alignment.CenterVertically + .clickable(onClick = onClick) , verticalAlignment = Alignment.CenterVertically ) { icon?.let { Spacer(modifier = Modifier.width(16.dp)) - Icon(it, contentDescription = null) + Icon(it , contentDescription = null) Spacer(modifier = Modifier.width(16.dp)) } Column( @@ -249,53 +238,21 @@ fun SwitchPreferenceItemWithDivider( .padding(16.dp) .weight(1f) ) { - Text(text = title, style = MaterialTheme.typography.titleLarge) - Text(text = summary, style = MaterialTheme.typography.bodyMedium) + Text(text = title , style = MaterialTheme.typography.titleLarge) + Text(text = summary , style = MaterialTheme.typography.bodyMedium) } VerticalDivider( modifier = Modifier .height(32.dp) - .align(Alignment.CenterVertically), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f), + .align(Alignment.CenterVertically) , + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) , thickness = 1.dp ) - Switch( - checked = checked, onCheckedChange = { isChecked -> - onCheckedChange(isChecked) - onSwitchClick(isChecked) - }, modifier = Modifier.padding(16.dp) - ) + Switch(checked = checked , onCheckedChange = { isChecked -> + onCheckedChange(isChecked) + onSwitchClick(isChecked) + } , modifier = Modifier.padding(16.dp)) } -} - -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/englishwithlidia/plus/utils/drawable/ImageUtils.kt b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/drawable/ImageUtils.kt new file mode 100644 index 0000000..7c4698d --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/englishwithlidia/plus/utils/drawable/ImageUtils.kt @@ -0,0 +1,24 @@ +package com.d4rk.englishwithlidia.plus.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/build.gradle.kts b/build.gradle.kts index 690ed07..29569b6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,5 @@ plugins { alias(libs.plugins.googlePlayServices) apply false alias(libs.plugins.googleFirebase) apply false alias(libs.plugins.googleOssServices) apply false - alias(libs.plugins.googleDaggerHilt) apply false alias(libs.plugins.devToolsKsp) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b76bb1a..361137f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,6 @@ billing = "7.0.0" datastoreCore = "1.1.1" glide = "4.16.0" graphicsShapes = "1.0.0-beta01" -hilt = "2.44" lifecycle = "2.8.3" lottieCompose = "4.0.0" material = "1.12.0" @@ -67,8 +66,6 @@ androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" } androidx-ui = { module = "androidx.compose.ui:ui" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } -hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } -hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } @@ -97,7 +94,6 @@ jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "k jetbrainsKotlinParcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", 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" } -googleDaggerHilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } 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