Skip to content

Commit

Permalink
feat [onboarding]: add onboarding functionality (cont.)
Browse files Browse the repository at this point in the history
  • Loading branch information
kabirnayeem99 committed Feb 12, 2024
1 parent b5eac58 commit 37fef00
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ class PreferenceDataSource @Inject constructor(private val context: Context) {
}
}

/**
* Saves the preferred fiqh of the user to local storage
*
* @param fiqh Fiqh - This is the object that contains the fiqh information.
*/
suspend fun savePreferredFiqh(fiqh: Fiqh) {
withContext(Dispatchers.IO) {
try {
Expand All @@ -63,12 +58,6 @@ class PreferenceDataSource @Inject constructor(private val context: Context) {
}
}

/**
* Gets the selected [Fiqh] by the user, which can be either Hanafi, Shafii, Maliki, Hanbali
* or unknown
*
* @return Fiqh - the Fiqh the user has selected before.
*/
suspend fun getPreferredFiqh(): Fiqh {
return withContext(Dispatchers.IO) {
try {
Expand Down Expand Up @@ -100,25 +89,17 @@ class PreferenceDataSource @Inject constructor(private val context: Context) {

isFirstTime || selectedFiqh.isBlank()
} catch (e: Exception) {
Timber.e(e)
true
}
}
}


/**
* Takes a lambda as a parameter, and calls it with a SharedPreferences.Editor as a parameter
*
* @param operation This is a lambda function that takes a SharedPreferences.Editor as a parameter
* and returns nothing.
*/
private inline fun SharedPreferences.edit(operation: (SharedPreferences.Editor) -> Unit) {
val editor = this.edit()
operation(editor)
editor.apply()
}


}

private const val TAG = "PreferenceDataSource"
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ class QuestionAnswerRepositoryImpl
val fiqh = getCurrentFiqh()

val lastSyncedPage = preferenceDataSource.getCurrentFiqhLastPageSynced()
val firstTime = lastSyncedPage < 1
val newStartingPage = lastSyncedPage + 1
val newLastSyncingPage = newStartingPage + 10
val newLastSyncingPage = newStartingPage + (if (firstTime) 2 else 10)

for (page in newStartingPage..newLastSyncingPage) {

Expand All @@ -64,7 +65,7 @@ class QuestionAnswerRepositoryImpl
delay(Random.nextLong((index + 1) * 100L))
val questionDetailed = remoteDataSource.getDetailedQuestionAndAnswer(q.url)
localDataSource.cacheQuestionDetail(questionDetailed)
currentProgress++
if (firstTime) currentProgress += 50 else currentProgress++
setProgress(currentProgress)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.work.ForegroundInfo
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters
Expand All @@ -22,6 +23,7 @@ import io.github.kabirnayeem99.islamqaorg.domain.repository.QuestionAnswerReposi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject

Expand Down Expand Up @@ -81,30 +83,46 @@ class BackgroundQAListFetcherWorker @AssistedInject constructor(

private const val CHANNEL_ID = "SYNC_NOTIFICATION"
private const val NOTIFICATION_ID = 1
suspend fun enqueue(
workManager: WorkManager, questionAnswerRepository: QuestionAnswerRepository
suspend fun enqueuePeriodically(
workManager: WorkManager,
questionAnswerRepository: QuestionAnswerRepository,
onSuccess: suspend () -> Unit = {},
onFailure: suspend () -> Unit = {},
) {
try {
val fiqh = questionAnswerRepository.getCurrentFiqh()
val constraintsBuilder = Constraints.Builder()
constraintsBuilder.apply {
setRequiredNetworkType(NetworkType.CONNECTED)
setRequiresBatteryNotLow(true)
setRequiresStorageNotLow(true)
}
val constraints = constraintsBuilder.build()

val periodicRequestBuilder = PeriodicWorkRequest.Builder(
BackgroundQAListFetcherWorker::class.java, SYNC_INTERVAL_HOURS, TimeUnit.HOURS
)
val tag = TAG + "->" + fiqh.paramName
val id = UUID.randomUUID()
periodicRequestBuilder.apply {
setConstraints(constraints)
addTag(tag)
setId(id)
}
val request = periodicRequestBuilder.build()
workManager.enqueueUniquePeriodicWork(tag, ExistingPeriodicWorkPolicy.KEEP, request)
workManager.enqueueUniquePeriodicWork(
tag, ExistingPeriodicWorkPolicy.KEEP, request
)

Timber.d("Scheduled $tag successfully")

workManager.getWorkInfosByTagFlow(tag).collect { workInfoList ->
when (workInfoList.firstOrNull { workInfo -> workInfo.id == id }?.state) {
WorkInfo.State.SUCCEEDED -> onSuccess()
WorkInfo.State.FAILED -> onFailure()
WorkInfo.State.BLOCKED -> onFailure()
WorkInfo.State.CANCELLED -> onFailure()
else -> Unit
}
}
} catch (e: Exception) {
Timber.e("schedulePeriodicSync: ${e.localizedMessage}", e)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.kabirnayeem99.islamqaorg.domain.useCase

import io.github.kabirnayeem99.islamqaorg.domain.repository.SettingsRepository
import javax.inject.Inject

class DetermineIfFirstTime @Inject constructor(private val settingsRepository: SettingsRepository) {
suspend operator fun invoke(): Boolean = settingsRepository.determineIfFirstTime()
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class FetchAndSavePeriodically
) {

suspend operator fun invoke() =
BackgroundQAListFetcherWorker.enqueue(workManager, questionAnswerRepository)

BackgroundQAListFetcherWorker.enqueuePeriodically(workManager, questionAnswerRepository)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.github.kabirnayeem99.islamqaorg.domain.useCase

import androidx.work.WorkManager
import io.github.kabirnayeem99.islamqaorg.common.base.Resource
import io.github.kabirnayeem99.islamqaorg.data.workers.BackgroundQAListFetcherWorker
import io.github.kabirnayeem99.islamqaorg.domain.entity.Fiqh
import io.github.kabirnayeem99.islamqaorg.domain.repository.QuestionAnswerRepository
import io.github.kabirnayeem99.islamqaorg.domain.repository.SettingsRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import javax.inject.Inject

class OnboardUser @Inject constructor(
private val settingsRepo: SettingsRepository,
private val qaRepo: QuestionAnswerRepository,
private val workManager: WorkManager,
) {
suspend operator fun invoke(fiqh: Fiqh): Flow<Resource<Boolean>> {
return flow {
emit(Resource.Loading())
settingsRepo.savePreferredFiqh(fiqh)
BackgroundQAListFetcherWorker.enqueuePeriodically(
workManager,
qaRepo,
onSuccess = {
settingsRepo.markAsOpened()
emit(Resource.Success(true))
},
onFailure = {
emit(Resource.Error<Boolean>("Failed to start fetching resources."))
},
)
}.catch { e ->
emit(Resource.Error(e.localizedMessage ?: "Error"))
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fun SettingsScreen(


@Composable
private fun MadhabSettingsItem(selectedFiqh: Fiqh, onFiqhSelected: (Fiqh) -> Unit) {
fun MadhabSettingsItem(selectedFiqh: Fiqh, onFiqhSelected: (Fiqh) -> Unit) {

Box(
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
Expand All @@ -21,6 +22,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -33,6 +35,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
Expand All @@ -46,6 +49,13 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import io.github.kabirnayeem99.islamqaorg.BuildConfig
import io.github.kabirnayeem99.islamqaorg.R
import io.github.kabirnayeem99.islamqaorg.domain.entity.Fiqh
import io.github.kabirnayeem99.islamqaorg.ui.settings.MadhabSettingsItem
import io.github.kabirnayeem99.islamqaorg.ui.start.NavigationState.CloseApp
import io.github.kabirnayeem99.islamqaorg.ui.start.NavigationState.FetchingResources
import io.github.kabirnayeem99.islamqaorg.ui.start.NavigationState.GoToHome
import io.github.kabirnayeem99.islamqaorg.ui.start.NavigationState.NonsensicalLoading
import io.github.kabirnayeem99.islamqaorg.ui.start.NavigationState.ShowFiqhOption
import io.github.kabirnayeem99.islamqaorg.ui.theme.ArabicFontFamily
import kotlinx.coroutines.delay

Expand All @@ -60,6 +70,8 @@ fun StartScreen(

var centerAppLogoVisibility by remember { mutableStateOf(false) }
var appVersionVisibility by remember { mutableStateOf(false) }
var fiqhOptionVisibility by remember { mutableStateOf(false) }
var progressBarVisibility by remember { mutableStateOf(false) }
var geometryVisibility by remember { mutableStateOf(false) }
var permissionAccepted by remember { mutableStateOf(false) }

Expand All @@ -80,8 +92,20 @@ fun StartScreen(
viewModel.syncQuestionsAndAnswers()
viewModel.navEvent.collect { navEvent ->
when (navEvent) {
NavigationState.CloseApp -> onCloseApp()
NavigationState.GoToHome -> {
CloseApp -> {
delay(SPLASH_SCREEN_DURATION / 8)
centerAppLogoVisibility = false
delay(SPLASH_SCREEN_DURATION / 8)
geometryVisibility = false
delay(SPLASH_SCREEN_DURATION / 8)
appVersionVisibility = false
delay(SPLASH_SCREEN_DURATION / 4)
delay(SPLASH_SCREEN_DURATION / 4)
onTimeUp()
onCloseApp()
}

GoToHome -> {
delay(SPLASH_SCREEN_DURATION / 8)
centerAppLogoVisibility = true
delay(SPLASH_SCREEN_DURATION / 8)
Expand All @@ -93,7 +117,7 @@ fun StartScreen(
onTimeUp()
}

NavigationState.KeepLoading -> {
NonsensicalLoading -> {
delay(SPLASH_SCREEN_DURATION / 6)
centerAppLogoVisibility = true
delay(SPLASH_SCREEN_DURATION / 6)
Expand All @@ -103,6 +127,31 @@ fun StartScreen(
delay(SPLASH_SCREEN_DURATION / 3)
delay(SPLASH_SCREEN_DURATION / 3)
}

ShowFiqhOption -> {
delay(SPLASH_SCREEN_DURATION / 6)
centerAppLogoVisibility = false
delay(SPLASH_SCREEN_DURATION / 6)
geometryVisibility = false
delay(SPLASH_SCREEN_DURATION / 6)
appVersionVisibility = false
delay(SPLASH_SCREEN_DURATION / 3)
delay(SPLASH_SCREEN_DURATION / 3)
fiqhOptionVisibility = true
}

FetchingResources -> {
fiqhOptionVisibility = false
delay(SPLASH_SCREEN_DURATION / 6)
centerAppLogoVisibility = true
delay(SPLASH_SCREEN_DURATION / 6)
geometryVisibility = true
delay(SPLASH_SCREEN_DURATION / 6)
appVersionVisibility = false
delay(SPLASH_SCREEN_DURATION / 3)
delay(SPLASH_SCREEN_DURATION / 3)
progressBarVisibility = true
}
}
}
} else {
Expand All @@ -125,10 +174,6 @@ fun StartScreen(
), label = "INFINITE_TRANSITION_ANGLE"
)

LaunchedEffect(true) {

}

BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
Expand Down Expand Up @@ -186,6 +231,14 @@ fun StartScreen(
}
}

AnimatedVisibility(
visible = fiqhOptionVisibility, enter = slideInVertically() + fadeIn()
) {
MadhabSettingsItem(Fiqh.UNKNOWN) { fiqh ->
viewModel.onFiqhOptionSelected(fiqh)
}
}

AnimatedVisibility(
visible = appVersionVisibility, enter = slideInHorizontally() + fadeIn()
) {
Expand All @@ -209,5 +262,19 @@ fun StartScreen(
)
}
}

AnimatedVisibility(
visible = progressBarVisibility,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(start = 24.dp, end = 24.dp, bottom = 60.dp),
enter = slideInHorizontally() + fadeIn()
) {
LinearProgressIndicator(
trackColor = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6F),
color = MaterialTheme.colorScheme.onPrimaryContainer,
strokeCap = StrokeCap.Round,
)
}
}
}
Loading

0 comments on commit 37fef00

Please sign in to comment.