diff --git a/.gitignore b/.gitignore index af2da2e1..0e50999d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,11 @@ /.idea/libraries /.idea/modules.xml /.idea/workspace.xml -/.idea/navEditor.xml +/.idea/navEditor.xmlc /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild .cxx -local.properties +local.properties \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f194d806..00d010f7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,7 +26,7 @@ android { try { file(rootProject.file("local.properties")).inputStream() .use { localProperties.load(it) } - } catch (e: Exception) { + } catch (_: Exception) { println("local.properties not found, using default values") } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt index 81c30d55..538213fa 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/deeplink/classes/JoinClassScreen.kt @@ -23,9 +23,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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.draw.clip @@ -35,25 +32,19 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.AsyncImage +import com.pwhs.quickmem.R import com.pwhs.quickmem.domain.model.classes.GetClassDetailResponseModel import com.pwhs.quickmem.presentation.ads.BannerAds import com.pwhs.quickmem.presentation.app.settings.component.SettingCard import com.pwhs.quickmem.presentation.app.settings.component.SettingItem import com.pwhs.quickmem.presentation.app.settings.component.SettingTitleSection import com.pwhs.quickmem.presentation.component.LoadingOverlay -import com.pwhs.quickmem.util.ads.AdsUtil.interstitialAds import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.generated.destinations.ClassDetailScreenDestination import com.ramcosta.composedestinations.generated.destinations.UserDetailScreenDestination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.revenuecat.purchases.CustomerInfo -import com.revenuecat.purchases.Purchases -import com.revenuecat.purchases.PurchasesError -import com.revenuecat.purchases.interfaces.ReceiveCustomerInfoCallback -import timber.log.Timber -import com.pwhs.quickmem.R @Destination( navArgs = JoinClassArgs::class @@ -92,8 +83,10 @@ fun JoinClassScreen( } JoinClassUiEvent.UnAuthorized -> { - Toast.makeText(context, - context.getString(R.string.txt_unauthorized), Toast.LENGTH_SHORT).show() + Toast.makeText( + context, + context.getString(R.string.txt_unauthorized), Toast.LENGTH_SHORT + ).show() navigator.navigate(NavGraphs.root) { popUpTo(NavGraphs.root) { saveState = false @@ -104,8 +97,10 @@ fun JoinClassScreen( } JoinClassUiEvent.NotFound -> { - Toast.makeText(context, - context.getString(R.string.txt_class_not_found), Toast.LENGTH_SHORT).show() + Toast.makeText( + context, + context.getString(R.string.txt_class_not_found), Toast.LENGTH_SHORT + ).show() navigator.navigate(NavGraphs.root) { popUpTo(NavGraphs.root) { saveState = false @@ -152,21 +147,6 @@ fun JoinClass( onBackHome: () -> Unit = {}, onOwnerClick: () -> Unit = {} ) { - var customer: CustomerInfo? by remember { mutableStateOf(null) } - val context = LocalContext.current - LaunchedEffect(key1 = true) { - Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback { - override fun onError(error: PurchasesError) { - Timber.e("Error getting customer info: $error") - } - - override fun onReceived(customerInfo: CustomerInfo) { - Timber.d("Customer info: $customerInfo") - customer = customerInfo - } - - }) - } Scaffold( topBar = { CenterAlignedTopAppBar( @@ -199,7 +179,7 @@ fun JoinClass( modifier = Modifier.padding(16.dp) ) { SettingItem( - title = "Name", + title = stringResource(R.string.txt_title), subtitle = classDetailResponseModel?.title ?: "", showArrow = false ) @@ -244,12 +224,7 @@ fun JoinClass( item { Button( - onClick = { - val isSubscribed = customer?.activeSubscriptions?.isNotEmpty() == true - interstitialAds(context, isSubscribed) { - onJoinClass() - } - }, + onClick = onJoinClass, modifier = Modifier.padding(16.dp) ) { Text(text = stringResource(R.string.txt_join_class)) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt index b8d48a25..1157c104 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -56,7 +57,6 @@ import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.generated.destinations.StudySetDetailScreenDestination import com.ramcosta.composedestinations.generated.destinations.UserDetailScreenDestination -import com.ramcosta.composedestinations.generated.destinations.UserDetailScreenDestination.invoke import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.revenuecat.purchases.CustomerInfo @@ -87,6 +87,7 @@ fun ExploreScreen( Toast.LENGTH_SHORT ).show() } + is ExploreUiEvent.Error -> { Toast.makeText( context, @@ -164,7 +165,7 @@ fun Explore( onDifficultyLevelChange: (DifficultyLevel) -> Unit = {}, onCreateStudySet: () -> Unit = {}, onEarnCoins: () -> Unit = {}, - @StringRes errorMessage: Int? = null, + @StringRes errorMessage: Int? = null, coins: Int = 0, customerInfo: CustomerInfo? = null, ) { @@ -174,7 +175,7 @@ fun Explore( stringResource(R.string.txt_top_streak), ) val context = LocalContext.current - + var isGettingAds by rememberSaveable { mutableStateOf(false) } Scaffold( modifier = modifier, @@ -195,7 +196,7 @@ fun Explore( ) { Icon( imageVector = Icons.Default.Refresh, - contentDescription = "Refresh", + contentDescription = stringResource(R.string.txt_refresh), ) } } else { @@ -216,22 +217,25 @@ fun Explore( ) Image( painter = painterResource(id = R.drawable.ic_coin), - contentDescription = "Coins", + contentDescription = stringResource(R.string.txt_coin), modifier = Modifier.size(24.dp), contentScale = ContentScale.Crop ) if (customerInfo?.activeSubscriptions?.isNotEmpty() == false) { Icon( imageVector = Icons.Default.Add, - contentDescription = "Add", + contentDescription = stringResource(R.string.txt_add), tint = colorScheme.primary, modifier = Modifier .size(24.dp) .clickable { + isGettingAds = true AdsUtil.rewardedInterstitialAd( context, - onEarnCoins - ) + ) { + onEarnCoins() + isGettingAds = false + } }, ) } @@ -312,7 +316,7 @@ fun Explore( ) } } - LoadingOverlay(isLoading = isLoading) + LoadingOverlay(isLoading = isLoading || isGettingAds) } } } diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt index 9d80a027..01b7cc97 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/ExploreViewModel.kt @@ -180,7 +180,14 @@ class ExploreViewModel @Inject constructor( ) } if (_uiState.value.customerInfo?.activeSubscriptions?.isNotEmpty() == false) { - updateCoins(coinAction = CoinAction.SUBTRACT) + updateCoins( + coinAction = CoinAction.SUBTRACT, + coin = when (uiState.value.numberOfFlashcards) { + in 1..10 -> 1 + in 11..20 -> 2 + else -> 3 + } + ) } _uiEvent.send( ExploreUiEvent.CreatedStudySet( diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt index 905e20b5..801abd1a 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/explore/create_study_set_ai/CreateStudySetAITab.kt @@ -102,11 +102,26 @@ fun CreateStudySetAITab( ) { Icon( painter = painterResource(R.drawable.ic_sparkling), - contentDescription = stringResource(R.string.txt_create_study_set_with_ai_minus_one), + contentDescription = "Create", ) Text( text = when (isPlus) { - false -> stringResource(R.string.txt_create_study_set_with_ai_minus_one) + false -> when (numberOfFlashcards) { + in 1..10 -> stringResource( + R.string.txt_create_study_set_ai_now, + 1 + ) + + in 11..20 -> stringResource( + R.string.txt_create_study_set_ai_now, + 2 + ) + + else -> { + stringResource(R.string.txt_create_study_set_ai_now, 3) + } + } + else -> stringResource( R.string.txt_create_study_set_with_ai, ) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/StudySetFlipCard.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/StudySetFlipCard.kt index e844334d..be6f567f 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/StudySetFlipCard.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/material/StudySetFlipCard.kt @@ -27,20 +27,26 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.pwhs.quickmem.R import com.pwhs.quickmem.presentation.component.ShowImageDialog @Composable -fun calculateDynamicFontSize(text: String): androidx.compose.ui.unit.TextUnit { +fun calculateDynamicFontSize(text: String): TextUnit { return when { - text.length <= 20 -> 24.sp text.length <= 50 -> 20.sp - else -> 16.sp + text.length <= 100 -> 18.sp + text.length <= 150 -> 16.sp + text.length <= 200 -> 14.sp + else -> 12.sp } } @@ -136,6 +142,7 @@ fun StudySetFlipCard( style = MaterialTheme.typography.bodyMedium.copy( fontSize = frontTextSize, color = colorScheme.onBackground, + fontWeight = FontWeight.Normal, textAlign = when { backImage != null -> TextAlign.Start else -> TextAlign.Center @@ -149,7 +156,10 @@ fun StudySetFlipCard( backImage?.let { if (it.isNotEmpty()) { AsyncImage( - model = it, + model = ImageRequest.Builder(LocalContext.current) + .data(it) + .error(R.drawable.ic_image_error) + .build(), contentDescription = null, modifier = Modifier .width(100.dp) diff --git a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/progress/ProgressTabScreen.kt b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/progress/ProgressTabScreen.kt index 0a0766c9..2bce501f 100644 --- a/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/progress/ProgressTabScreen.kt +++ b/app/src/main/java/com/pwhs/quickmem/presentation/app/study_set/detail/progress/ProgressTabScreen.kt @@ -2,6 +2,7 @@ package com.pwhs.quickmem.presentation.app.study_set.detail.progress import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -113,6 +114,9 @@ fun ProgressTabScreen( ) } } + item { + Spacer(modifier = Modifier.padding(60.dp)) + } } } } diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 154e4001..d9b8f179 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -263,7 +263,6 @@ Huy chương đồng Bảng xếp hạng 10 chuỗi thành tích cao nhất Duy trì chuỗi để leo bảng xếp hạng! - Tạo (-1) Cảnh báo: AI có thể không tạo thẻ học chính xác hoặc đầy đủ. Hãy kiểm tra lại nếu bạn thấy cần thiết. Tiêu đề (bắt buộc) Mô tả (tùy chọn) @@ -616,4 +615,7 @@ Vui lòng đăng nhập lại! Đánh dấu thông báo là đã đọc thất bại Tải thông báo thất bại + Tải lại + Xu + Tạo (-%1$s) \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b7ceec2e..f998068d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -265,7 +265,6 @@ Bronze Medal Top 10 highest streak leaderboard Keep up the streak to climb the leaderboard! - Create (-1) Warning: AI may not generate accurate or complete flashcards. Please review if unsure. Title (required) Description (optional) @@ -619,4 +618,7 @@ Please login again! Failed to mark notification as read Failed to load notifications + Refresh + Coin + Create (-%1$s) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c3335ba5..32c323ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,17 +1,7 @@ [versions] # default versions compileSdk = "35" -composeCharts = "0.0.18" -credentials = "1.3.0" -credentialsPlayServicesAuth = "1.3.0" -googleid = "1.1.1" minSdk = "29" -pagingRuntimeKtx = "3.3.4" -pagingCompose = "3.3.4" -playServicesAuth = "21.3.0" -revenuecat = "8.10.1" -roomRuntime = "2.6.1" -roomCompiler = "2.6.1" targetSdk = "35" versionCode = "8" versionName = "1.6(1)" @@ -24,7 +14,7 @@ easycrop = "0.1.1" easyvalidationCore = "1.0.4" firebaseBom = "33.7.0" firebaseMessagingKtx = "24.1.0" -foundation = "1.7.5" +foundation = "1.7.6" kotlin = "2.0.20" coreKtx = "1.15.0" junit = "4.13.2" @@ -32,12 +22,22 @@ junitVersion = "1.2.1" espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.9.3" -composeBom = "2024.11.00" +composeBom = "2024.12.01" ksp = "2.0.20-1.0.25" lottieCompose = "6.4.2-SNAPSHOT" -materialIconsExtended = "1.7.5" +materialIconsExtended = "1.7.6" +pagingRuntimeKtx = "3.3.5" +pagingCompose = "3.3.5" +playServicesAuth = "21.3.0" +revenuecat = "8.10.1" +roomRuntime = "2.6.1" +roomCompiler = "2.6.1" +composeCharts = "0.0.18" +credentials = "1.3.0" +credentialsPlayServicesAuth = "1.3.0" +googleid = "1.1.1" # Serialization -runtime = "1.7.5" +runtime = "1.7.6" serialization = "2.0.20" serialization-json = "1.7.1" serialization-core = "1.7.1" @@ -58,7 +58,7 @@ composeAnimations = "1.11.6" workRuntime = "2.10.0" accompanist = "0.34.0" jakewhartonTimber = "5.0.1" -uiTextGoogleFonts = "1.7.5" +uiTextGoogleFonts = "1.7.6" # Retrofit retrofit = "2.11.0" loggingInterceptor = "4.12.0"