Created
May 27, 2022 09:31
-
-
Save mrsasha/891284ff631e8d82f9bb822fdace337b to your computer and use it in GitHub Desktop.
HomeViewModel v1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package lu.gian.uniwhere.features.home.ui | |
import androidx.lifecycle.LiveData | |
import androidx.lifecycle.MutableLiveData | |
import androidx.lifecycle.viewModelScope | |
import com.google.firebase.iid.FirebaseInstanceId | |
import com.uniwhere.kmp.elephas.localdatasource.LocalDataSource | |
import com.uniwhere.kmp.elephas.model.ExamStatsAverageType | |
import com.uniwhere.kmp.elephas.model.UWErrorCodeClass | |
import com.uniwhere.kmp.elephas.model.UniAccountWrapper | |
import com.uniwhere.kmp.elephas.model.UniversitySyncStatus | |
import com.uniwhere.kmp.elephas.settings.SettingsRepository | |
import com.uniwhere.kmp.hydra.be.dto.uniservice.ServiceDTO | |
import com.uniwhere.kmp.hydra.be.dto.uniservice.entity.EntityType | |
import com.uniwhere.kmp.hydra.uwerror.UWErrorCode | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.launch | |
import lu.gian.uniwhere.core.base.BaseViewModel | |
import lu.gian.uniwhere.core.data.AccountUtils | |
import lu.gian.uniwhere.core.data.CoreRepository | |
import lu.gian.uniwhere.core.error.UWErrorResourcesMapper | |
import lu.gian.uniwhere.core.extensions.dto.ectsDone | |
import lu.gian.uniwhere.core.extensions.dto.ectsTotal | |
import lu.gian.uniwhere.core.extensions.dto.getDate | |
import lu.gian.uniwhere.core.extensions.dto.hasBeenPaid | |
import lu.gian.uniwhere.core.extensions.dto.hasGrade | |
import lu.gian.uniwhere.core.extensions.formatNoDecimal | |
import lu.gian.uniwhere.core.extensions.formatTwoDecimal | |
import lu.gian.uniwhere.core.utils.Event | |
import lu.gian.uniwhere.core.utils.ResourceProvider | |
import lu.gian.uniwhere.core.utils.UWDates | |
import lu.gian.uniwhere.core.utils.Utils | |
import lu.gian.uniwhere.features.home.R | |
import lu.gian.uniwhere.features.home.data.HomeUtils | |
import lu.gian.uniwhere.features.home.data.model.home.FakeHomeState | |
import lu.gian.uniwhere.features.home.data.model.home.HomeDestination | |
import lu.gian.uniwhere.features.home.data.model.home.HomeHeader | |
import lu.gian.uniwhere.features.home.data.model.home.HomeLeanCard | |
import lu.gian.uniwhere.features.home.data.model.home.HomeTileItem | |
import lu.gian.uniwhere.features.home.data.model.home.HomeTileItemType | |
import lu.gian.uniwhere.features.home.data.model.home.IconProgressTile | |
import lu.gian.uniwhere.features.home.data.model.home.IconTitleTile | |
import lu.gian.uniwhere.features.home.data.model.home.QuickActionCollection | |
import lu.gian.uniwhere.features.home.data.model.home.QuickActionDestination | |
import lu.gian.uniwhere.features.home.data.model.home.QuickActionTile | |
import lu.gian.uniwhere.features.home.data.model.home.StartTutorial | |
import lu.gian.uniwhere.features.home.data.model.home.StatesOfHome | |
import lu.gian.uniwhere.libraries.analytics.AnalyticsHelper | |
import lu.gian.uniwhere.libraries.analytics.AnalyticsIdentifyKeys | |
import lu.gian.uniwhere.libraries.analytics.AnalyticsTrackActions | |
import lu.gian.uniwhere.libraries.analytics.AnalyticsTrackPropertyKeys | |
import lu.gian.uniwhere.libraries.analytics.getSegmentFormattedTime | |
import lu.gian.uniwhere.libraries.uicomponents.data.model.tile.HeaderTile | |
import lu.gian.uniwhere.libraries.uniservice.model.FirebaseConfigParam | |
import lu.gian.uniwhere.libraries.uniservice.model.FirebaseConfigResult | |
import lu.gian.uniwhere.libraries.uniservice.net.RemoteConfigRepository | |
import lu.gian.uniwhere.libraries.uniservice.net.UniServiceRepository | |
import lu.gian.uniwhere.libraries.uwerror.UWError | |
import retrofit2.HttpException | |
import timber.log.Timber | |
import java.net.ConnectException | |
import java.net.SocketTimeoutException | |
import java.net.UnknownHostException | |
import java.util.Calendar | |
import javax.inject.Inject | |
import javax.net.ssl.SSLHandshakeException | |
class HomeViewModel @Inject constructor( | |
val analyticsHelper: AnalyticsHelper, | |
val settingsRepository: SettingsRepository, | |
private val resourceProvider: ResourceProvider, | |
private val localDataSource: LocalDataSource, | |
private val coreRepository: CoreRepository, | |
private val accountUtils: AccountUtils, | |
private val uniServiceRepository: UniServiceRepository, | |
private val remoteConfigRepository: RemoteConfigRepository, | |
private val settingRepository: SettingsRepository | |
) : BaseViewModel() { | |
private val _showStartTutorial = MutableLiveData<Event<StartTutorial>>() | |
val showStartTutorial: LiveData<Event<StartTutorial>> | |
get() = _showStartTutorial | |
private val _stateOfHome = MutableLiveData<StatesOfHome>() | |
val statesOfHome: LiveData<StatesOfHome> | |
get() = _stateOfHome | |
private var isUnsupportedService: Boolean = false | |
fun checkForUnsupportedUniversity(firstTime: Boolean) { | |
_stateOfHome.value = StatesOfHome.ShowLoading | |
viewModelScope.launch { | |
isUnsupportedService = isServiceUnsupported() | |
if (isUnsupportedService && firstTime) { | |
_stateOfHome.value = StatesOfHome.UnsupportedUniversity | |
} | |
sessionCheckup() | |
} | |
} | |
fun sessionCheckup() { | |
Timber.d("[CHECKUP] Starting Checkup") | |
if (wasAppUpdated()) { | |
val savedVersion = settingsRepository.savedAppVersion.get() | |
if (savedVersion <= 92000) { | |
// Identify user! | |
identifyOnSegment() | |
} | |
Timber.d("[sessionCheckup] : New app update detected") | |
} else { | |
Timber.d("[sessionCheckup] : App first start, begin checkup") | |
} | |
updateConf() | |
} | |
private fun updateConf() { | |
_stateOfHome.value = StatesOfHome.ShowLoading | |
Timber.d("[updateConf] Updating Bootstrap and Conf") | |
val apiAuth = localDataSource.getApiAuth() | |
apiAuth?.let { | |
Utils.initFacebookSDK() | |
log("[updateConf] Check Crashlytics") | |
} ?: { | |
// TODO What if apiAuth is null? | |
} | |
viewModelScope.launch { | |
try { | |
val configuration = coreRepository.getConf() | |
localDataSource.deleteConfiguration() | |
localDataSource.saveConfiguration(configuration) | |
// Update also the selected service | |
val actualService = localDataSource.getSelectedService() | |
val serviceCode = actualService?.serviceCode ?: "" | |
val updateService = coreRepository.getService(serviceCode) | |
localDataSource.saveSelectedService(updateService) | |
updateUser() | |
} catch (e: Exception) { | |
val error = when (e) { | |
is HttpException -> { | |
when (e.code()) { | |
401 -> { | |
performLogout() | |
return@launch | |
} | |
404 -> UWError.Builder(UWErrorCode.BACKEND_SERVICE_NOT_FOUND, e) | |
.setLocalDataSource(localDataSource) | |
.build() | |
else -> UWError.Builder(UWErrorCode.BACKEND_SERVER_ERROR, e) | |
.setLocalDataSource(localDataSource) | |
.build() | |
} | |
} | |
is UnknownHostException -> UWError.Builder(UWErrorCode.APP_OFFLINE, e) | |
.setLocalDataSource(localDataSource) | |
.build() | |
is ConnectException, is SocketTimeoutException, is SSLHandshakeException -> | |
UWError.Builder(UWErrorCode.APP_CONNECTION_FAILED, e) | |
.setLocalDataSource(localDataSource) | |
.build() | |
else -> UWError.Builder(UWErrorCode.APP_UNKNOWN_ERROR, e) | |
.setLocalDataSource(localDataSource) | |
.build() | |
} | |
_stateOfHome.value = StatesOfHome.Error | |
_banner.value = UWErrorResourcesMapper.raiseError(error) | |
_stateOfHome.value = StatesOfHome.HideLoading | |
Timber.e(error, "[updateConf] Error: $error") | |
} | |
} | |
} | |
private fun updateUser() { | |
Timber.d("[updateUser()] updating user") | |
// Reset cached value | |
coreRepository.currentUserDTO = null | |
viewModelScope.launch { | |
try { | |
val token = try { | |
FirebaseInstanceId.getInstance().instanceId.result?.token | |
} catch (e: Exception) { | |
null | |
} | |
Timber.d("[updateUser()] - Firebase Token -> $token") | |
val apiAuth = localDataSource.getApiAuth() | |
val userFromMemory = apiAuth?.user | |
val newUser = if (token != null && token != userFromMemory?.fcmToken) { | |
coreRepository.updateFcmToken(token) | |
} else { | |
coreRepository.getUser() | |
} | |
apiAuth?.copy(user = newUser)?.let { | |
localDataSource.saveApiAuth(it) | |
} | |
coreRepository.refreshReviewList() | |
dataUpdate() | |
} catch (error: Exception) { | |
handleAuthGenericError(error) | |
Timber.e(error, "[updateUser] Error: $error") | |
_stateOfHome.value = StatesOfHome.HideLoading | |
} | |
} | |
} | |
private fun dataUpdate() { | |
Timber.d("[dataUpdate()] updating data") | |
viewModelScope.launch(Dispatchers.Main) { | |
try { | |
val currentUniAccountWrapper = coreRepository.currentUniAccountWrapper | |
//TODO we need an IF to manage the other case | |
currentUniAccountWrapper?.universityAccountDTO?.let { account -> | |
val t = uniServiceRepository.getAllEntities(account.accountId) | |
Timber.d("dataUpdate - All entities fetched correctly") | |
Timber.v(t.toString()) | |
Timber.d("dataUpdate - University Account - ${account.toString()}") | |
localDataSource.getUniServiceTrackingData()?.let { trackData -> | |
val service = localDataSource.getSelectedService() | |
service?.let { ser -> | |
trackData.syncType?.let { syncType -> | |
analyticsHelper.trackEvent( | |
AnalyticsTrackActions.universityDataReceived, mapOf( | |
AnalyticsTrackPropertyKeys.status to "success", | |
AnalyticsTrackPropertyKeys.universityCode to ser.serviceCode, | |
AnalyticsTrackPropertyKeys.syncType to syncType, | |
AnalyticsTrackPropertyKeys.error to "none" | |
) | |
) | |
} | |
} | |
} | |
val keysIterator = t.keys() | |
val recapMessage = StringBuilder().append("Recap of found entities:\n") | |
while (keysIterator.hasNext()) { | |
val key = keysIterator.next() | |
try { | |
val entityType = EntityType.valueOf(key) | |
val jsonArray = t.getJSONArray(key) | |
uniServiceRepository.deserializeAndSaveEntity(entityType, jsonArray) | |
Timber.v(String.format("Entity %s successfully stored", key)) | |
Timber.v( | |
String.format( | |
"Found ${ | |
t.getJSONArray(key).length() | |
} items of entity: %s", key | |
) | |
) | |
recapMessage.append( | |
"\tFound ${t.getJSONArray(key).length()} items of entity: $key\n" | |
) | |
} catch (e: Exception) { | |
Timber.e(e, | |
String.format( | |
"Skipping storing entity %s because of the exception: %s", | |
key, | |
e.message | |
) | |
) | |
} | |
} | |
log(recapMessage.toString()) | |
//TODO why? don't we already load this above? | |
val universityAccount = coreRepository.getUniversityAccount(currentUniAccountWrapper.universityAccountDTO.accountId) | |
localDataSource.updateUniAccountDTO(universityAccount) | |
localDataSource.setSyncStatus( | |
UniversitySyncStatus.SYNCED, | |
universityAccount.accountId | |
) | |
settingsRepository.lastAutoRefresh.set(System.currentTimeMillis()) | |
val transcriptResult = coreRepository.getTranscript(universityAccount.accountId) | |
localDataSource.saveTranscript(transcriptResult) | |
settingsRepository.studyPlanRefreshRequired.set(true) | |
val examStats = coreRepository.getExamStats(universityAccount.accountId) | |
// delete previous exam stats, to be sure | |
localDataSource.deleteExamStats() | |
localDataSource.saveExamStats(examStats) | |
generateHomeItems() | |
} | |
} catch (e: Exception) { | |
Timber.e(e, "Error fetching all entities. Tried too many times already. Error: $e") | |
val error = when (e) { | |
is HttpException -> { | |
when (e.code()) { | |
401 -> UWError.Builder(UWErrorCode.BACKEND_LOGIN_FAILED, e) | |
else -> UWError.Builder(UWErrorCode.BACKEND_SERVER_ERROR, e) | |
} | |
} | |
is UnknownHostException -> UWError.Builder(UWErrorCode.APP_OFFLINE, e) | |
is ConnectException, is SocketTimeoutException, is SSLHandshakeException -> | |
UWError.Builder(UWErrorCode.APP_CONNECTION_FAILED, e) | |
else -> UWError.Builder(UWErrorCode.APP_UNKNOWN_ERROR, e) | |
}.context("ERROR", "Error fetching all entities. Tried too many times already") | |
.setLocalDataSource(localDataSource) | |
.build() | |
localDataSource.getUniServiceTrackingData()?.let { trackData -> | |
val service = localDataSource.getSelectedService() | |
service?.let { ser -> | |
trackData.syncType?.let { syncType -> | |
val errorCode = when { | |
error.getErrorClass() == UWErrorCodeClass.BACKEND -> "backend_failure" | |
error.getErrorClass() == UWErrorCodeClass.APP -> "network" | |
else -> "none" | |
} | |
analyticsHelper.trackEvent( | |
AnalyticsTrackActions.universityDataReceived, mapOf( | |
AnalyticsTrackPropertyKeys.status to "failure", | |
AnalyticsTrackPropertyKeys.universityCode to ser.serviceCode, | |
AnalyticsTrackPropertyKeys.syncType to syncType, | |
AnalyticsTrackPropertyKeys.error to errorCode | |
) | |
) | |
} | |
} | |
} | |
_stateOfHome.value = StatesOfHome.HideLoading | |
_banner.value = UWErrorResourcesMapper.raiseError(error) | |
} | |
} | |
tokenRefresh() | |
} | |
//TODO why now? | |
private fun tokenRefresh() { | |
Timber.d("[CHECK-UP] Refreshing token") | |
val lastUserUpdateTimestamp = settingsRepository.lastUserUpdate.get() | |
// 7 days | |
if ((System.currentTimeMillis() - lastUserUpdateTimestamp) > lu.gian.uniwhere.core.BuildConfig.USER_REFRESH_INTERVAL) { | |
viewModelScope.launch { | |
try { | |
val apiAuth = coreRepository.updateAccessToken() | |
localDataSource.saveApiAuth(apiAuth) | |
settingsRepository.lastUserUpdate.set(System.currentTimeMillis()) | |
generateHomeItems() | |
} catch (e: Exception) { | |
handleAuthGenericError(e) | |
} | |
} | |
} else { | |
Timber.d("[CHECK-UP] Token refresh not necessary, skipping") | |
generateHomeItems() | |
} | |
} | |
private fun isTimeForRating(ratingSettings: FirebaseConfigResult.RatingSettings) { | |
val uniAccount = coreRepository.currentUniAccountWrapper | |
Timber.d("Rating Settings - uniAccount : $uniAccount") | |
Timber.d("Rating Settings - uniAccountisNotNull : ${uniAccount != null}") | |
val ratingNotYetDone = settingRepository.ratingPopupShowed.get().not() | |
Timber.d("Rating Settings - alreadyDidIt : $ratingNotYetDone") | |
val isTimePassed = isTimePassedForRating(ratingSettings) | |
val isNumberOfAccess = isNumberOfAccessForRating(ratingSettings) | |
Timber.d("Rating Settings - ratingSettings.settings.androidCanShow : ${ratingSettings.settings.androidCanShow}") | |
val isWontReviewCountReached = !isWontReviewForRatingReached(ratingSettings) | |
Timber.d("Rating Settings - isWontReviewCountReached : $isWontReviewCountReached") | |
val isUniversityAvailableForRating: Boolean = uniAccount?.let { isUniversityAllowedForRating(ratingSettings, it) } ?: false | |
Timber.d("Rating Settings - isUniversityAvailableForRating : $isUniversityAvailableForRating") | |
if (uniAccount != null | |
&& isTimePassed | |
&& isNumberOfAccess | |
&& isWontReviewCountReached | |
&& ratingSettings.settings.androidCanShow | |
&& isUniversityAvailableForRating | |
&& ratingNotYetDone | |
) { | |
// Did it. | |
_stateOfHome.value = StatesOfHome.ShowRatingPopup | |
} | |
} | |
private fun isWontReviewForRatingReached(ratingSettings: FirebaseConfigResult.RatingSettings): Boolean { | |
return settingsRepository.dontWantReview.get() <= ratingSettings.settings.wontReviewMaxCount | |
} | |
private fun isNumberOfAccessForRating(ratingSettings: FirebaseConfigResult.RatingSettings): Boolean { | |
var accessesSoFar = settingRepository.accessCount.get() | |
if (accessesSoFar <= ratingSettings.settings.numberOfAccessBeforeRating) { | |
accessesSoFar++ | |
settingRepository.accessCount.set(accessesSoFar) | |
} | |
val numberOfAccess = accessesSoFar > ratingSettings.settings.numberOfAccessBeforeRating | |
Timber.d("Rating Settings - numberOfAccess : $numberOfAccess") | |
return numberOfAccess | |
} | |
private fun isTimePassedForRating(ratingSettings: FirebaseConfigResult.RatingSettings): Boolean { | |
var firstAccess = settingRepository.firstAccessTimestamp.get() | |
if (firstAccess == 0L) { | |
firstAccess = System.currentTimeMillis() | |
settingRepository.firstAccessTimestamp.set(firstAccess) | |
} | |
val timePassedFromLastAccess = firstAccess + ratingSettings.settings.millisDaysBeforeRating < System.currentTimeMillis() | |
Timber.d("Rating Settings - timePassedFromLastAccess : $timePassedFromLastAccess") | |
return timePassedFromLastAccess | |
} | |
private fun isUniversityAllowedForRating( | |
ratingSettings: FirebaseConfigResult.RatingSettings, | |
uniAccount: UniAccountWrapper | |
) = ratingSettings.settings.universityAllowed.any { university -> university.serviceCode == uniAccount.universityAccountDTO.serviceCode } | |
private fun generateStartTutorial() { | |
Timber.v("[generateStartTutorial]") | |
val selectedService = localDataSource.getSelectedService() | |
val serviceEnabled = selectedService?.enabled ?: false | |
val content = if (serviceEnabled) { | |
resourceProvider.getString(R.string.HOME_start_tutorial_content) | |
} else { | |
resourceProvider.getString(R.string.HOME_tutorial_uni_not_supported) | |
} | |
val connectBtn = if (serviceEnabled) { | |
resourceProvider.getString(R.string.HOME_start_tutorial_connect_title) | |
} else { | |
resourceProvider.getString(R.string.HOME_uni_not_supported_btn) | |
} | |
val startTutorial = StartTutorial( | |
title = resourceProvider.getString(R.string.HOME_start_tutorial_welcome), | |
content = content, | |
connectButtonText = connectBtn, | |
exploreButtonText = resourceProvider.getString(R.string.HOME_tutorial_explore_btn), | |
serviceEnabled = serviceEnabled, | |
color = resourceProvider.getColor(R.color.accent), | |
iconResId = R.drawable.ic_icon_user_check, | |
comingSoonUrl = selectedService?.comingSoonUrl | |
) | |
_showStartTutorial.value = Event(startTutorial) | |
} | |
private fun generateHomeItems() { | |
// recompute the tiles | |
Timber.d("[generateHomeItems] : Computing Homepage Tiles") | |
//items.clear() | |
_stateOfHome.value = StatesOfHome.ShowLoading | |
Timber.v("[generateHomeItems] Getting current UniAccount") | |
val uniAccount = coreRepository.currentUniAccountWrapper | |
if (uniAccount == null) { | |
val fakeState = FakeHomeState.getFakeHomeState( | |
settingsRepository, | |
accountUtils, | |
resourceProvider | |
) | |
generateStartTutorial() | |
Timber.v("[generateHomeItems] Setting default Homepage State") | |
_stateOfHome.value = fakeState | |
} else { | |
val items = mutableListOf<HomeTileItem>() | |
items.addAll(generateUniwhereDevelhopeTile()) | |
if (isServiceEnabled()) { | |
Timber.v("[generateHomeItems] - isServiceEnabled = ${isServiceEnabled()}") | |
generateNotSyncedTile()?.let { items.add(it) } | |
items.addAll(generateSyncIssueTile(uniAccount)) | |
} else { | |
val service = localDataSource.getSelectedService() | |
if (service != null) { | |
Timber.v("[generateHomeItems] - localDataSource.getSelectedService() = not null") | |
items.addAll(generateServiceInterruptedTile(service)) | |
} else { | |
Timber.v("[generateHomeItems] - localDataSource.getSelectedService() = null") | |
} | |
} | |
if (isUnsupportedService) { | |
items.add(generateUnsupportedUniTile()) | |
} | |
items.addAll(generatePerformanceTile()) | |
items.addAll(generateProgressionTile()) | |
items.addAll(generateQuickActions()) | |
items.addAll(generateAdministrationTile()) | |
items.add(HomeUtils.getLastUpdateTile(settingsRepository, resourceProvider)) | |
val state = StatesOfHome.HomeState( | |
items = items, | |
homeHeader = generateHomeHeader() | |
) | |
_stateOfHome.value = state | |
} | |
viewModelScope.launch { | |
checkRatingSettings() | |
} | |
} | |
private fun generateHomeHeader(): HomeHeader { | |
Timber.d("[CHECKUP] Generating Home Header") | |
val todayPhrases = HomeUtils.getTodayPhrases(resourceProvider, accountUtils) | |
return HomeHeader( | |
title = todayPhrases.first, | |
subtitle = todayPhrases.second | |
) | |
} | |
private fun generateNotSyncedTile(): HomeTileItem? { | |
val taxCount = localDataSource.getTaxes().size | |
val examsDoneCount = coreRepository.getDoneTranscripts().size | |
val examTodoCount = coreRepository.getToDoTranscripts().size | |
Timber.d("[generateNotSyncedTile] taxCount: $taxCount, examsDoneCount: $examsDoneCount, examTodoCount: $examTodoCount") | |
val sumCheck = taxCount + examsDoneCount + examTodoCount | |
return if (sumCheck == 0) { | |
Timber.v("[generateNotSyncedTile] NotSynced tile added") | |
HomeTileItem( | |
data = HomeLeanCard( | |
title = resourceProvider.getString(R.string.HOME_not_synced_uni_title), | |
content = resourceProvider.getString(R.string.HOME_not_synced_uni_description), | |
destination = HomeDestination.UNIWHERE_DEVELOP, | |
uniAccountId = null, | |
strokeColor = resourceProvider.getColor(R.color.yellow3) | |
), | |
itemType = HomeTileItemType.SYNC_TILE | |
) | |
} else { | |
null | |
} | |
} | |
private fun generateUnsupportedUniTile(): HomeTileItem { | |
Timber.d("[generateNotSyncedTile] NotSupportedUniversity tile added") | |
return HomeTileItem( | |
data = HomeLeanCard( | |
title = resourceProvider.getString(R.string.HOME_not_supported_uni_title), | |
content = resourceProvider.getString(R.string.HOME_not_supported_uni_description), | |
destination = HomeDestination.UNIWHERE_DEVELOP, | |
uniAccountId = null, | |
strokeColor = resourceProvider.getColor(R.color.yellow3), | |
iconId = R.drawable.ic_wip | |
), | |
itemType = HomeTileItemType.SYNC_TILE | |
) | |
} | |
private fun generateUniwhereDevelhopeTile(): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generateUniwhereDevelhopeTile] Generating Develhope Tile") | |
val isTileAlreadyShown = settingsRepository.uniwhereDevelhopeMessageShown.get() | |
val uniwhereAccount = localDataSource.getUniwhereAccount() | |
val creationDate = uniwhereAccount?.creationDate ?: -1L | |
val thresholdDateMillis = Calendar.getInstance().apply { | |
this[Calendar.YEAR] = 2021 | |
this[Calendar.MONTH] = Calendar.OCTOBER | |
this[Calendar.DAY_OF_MONTH] = 1 | |
}.timeInMillis | |
if (!isTileAlreadyShown && creationDate < thresholdDateMillis) { | |
items.add( | |
HomeTileItem( | |
data = HomeLeanCard( | |
title = resourceProvider.getString(R.string.HOME_uniwhere_develhope_title), | |
content = resourceProvider.getString(R.string.HOME_uniwhere_develhope_subtitle), | |
destination = HomeDestination.UNIWHERE_DEVELOP, | |
uniAccountId = null, | |
serviceName = null | |
), | |
itemType = HomeTileItemType.SYNC_TILE | |
) | |
) | |
} | |
return items | |
} | |
private fun generateSyncIssueTile(uniAccountWrapper: UniAccountWrapper): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generateSyncIssueTile] Generating Sync Issue Tile") | |
if (uniAccountWrapper.syncStatus != UniversitySyncStatus.SYNCED) { | |
items.add( | |
HomeTileItem( | |
data = HomeLeanCard( | |
title = resourceProvider.getString(R.string.HOME_sync_error_title), | |
content = resourceProvider.getString(R.string.HOME_sync_error_content), | |
destination = HomeDestination.ERROR_RESOLUTION, | |
uniAccountId = uniAccountWrapper.universityAccountDTO.accountId | |
), | |
itemType = HomeTileItemType.SYNC_TILE | |
) | |
) | |
} | |
return items | |
} | |
private fun generateServiceInterruptedTile(service: ServiceDTO): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generateServiceInterruptedTile] Generating Service Interrupted tile") | |
items.add( | |
HomeTileItem( | |
data = HomeLeanCard( | |
title = resourceProvider.getString( | |
R.string.HOME_disabled_uni_title, | |
service.serviceCode | |
), | |
content = resourceProvider.getString( | |
R.string.HOME_disabled_uni_small_description, | |
service.serviceCode | |
), | |
destination = HomeDestination.SERVICE_INTERRUPTED, | |
uniAccountId = null, | |
serviceName = service.serviceCode | |
), | |
itemType = HomeTileItemType.SYNC_TILE | |
) | |
) | |
return items | |
} | |
private fun generatePerformanceTile(): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generatePerformanceTile] Generating Performance tile") | |
val mean = getMean() | |
var content = resourceProvider.getString(R.string.HOME_tile_performance_content_no_data) | |
val stringsToBold = mutableListOf<String>() | |
// exam ratio | |
val examsDone = coreRepository.getDoneTranscripts() | |
val exams = coreRepository.getSavedTranscriptList() | |
val examRatio = if (exams.isEmpty()) { | |
0.0 | |
} else { | |
(examsDone.size.toDouble() / exams.size.toDouble()) * 100.0 | |
} | |
if (mean != 0.0) { | |
val gpaString = mean.formatTwoDecimal() | |
val examStats = localDataSource.getExamStats() | |
val topString = (examStats?.gpaPeersPerformance ?: 0).toString() | |
stringsToBold.add(gpaString) | |
stringsToBold.add(topString) | |
content = resourceProvider.getString( | |
R.string.HOME_tile_performance_content, | |
gpaString, | |
"$topString %" | |
) | |
} else { | |
stringsToBold.add(resourceProvider.getString(R.string.HOME_tile_performance_content_no_data_to_bolditize)) | |
} | |
val performanceTile = IconProgressTile( | |
title = resourceProvider.getString(R.string.HOME_tile_performance_title), | |
subtitle = content, | |
stringsToBold = stringsToBold, | |
color = resourceProvider.getColor(R.color.accent), | |
iconResId = R.drawable.ic_icon_cpu, | |
progress = examRatio.toInt(), | |
progressString = "${examsDone.size}/${exams.size}", | |
destination = HomeDestination.GPA | |
) | |
items.add( | |
HomeTileItem( | |
data = performanceTile, | |
itemType = HomeTileItemType.ICON_PROGRESS_TILE | |
) | |
) | |
return items | |
} | |
private fun generateProgressionTile(): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generateProgressionTile] Generating Progression tile") | |
val examStats = localDataSource.getExamStats() | |
val ectsTotal = examStats?.ectsTotal() ?: 0.0 | |
val ectsDone = examStats?.ectsDone() ?: 0.0 | |
val progress = if (ectsTotal == 0.0) { | |
0.0 | |
} else { | |
(ectsDone / ectsTotal) * 100.0 | |
} | |
val ectsString = "$ectsDone ${resourceProvider.getString(R.string.EXAMS_ects_label)}" | |
val progressionContent = resourceProvider.getString( | |
R.string.HOME_tile_progression_content, | |
ectsString, ectsTotal.toString() | |
) | |
val progressTile = IconProgressTile( | |
title = resourceProvider.getString(R.string.HOME_tile_progression_title), | |
subtitle = progressionContent, | |
stringsToBold = listOf(ectsString), | |
color = resourceProvider.getColor(R.color.accent), | |
iconResId = R.drawable.ic_icon_archive, | |
progress = progress.toInt(), | |
progressString = "${progress.formatNoDecimal()}%", | |
destination = HomeDestination.TRANSCRIPT | |
) | |
items.add( | |
HomeTileItem( | |
data = progressTile, | |
itemType = HomeTileItemType.ICON_PROGRESS_TILE | |
) | |
) | |
return items | |
} | |
private fun generateQuickActions(): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generateQuickActions] Generating Quick Actions Tile") | |
// header | |
items.add( | |
HomeTileItem( | |
data = HeaderTile( | |
title = resourceProvider.getString(R.string.HOME_quick_actions) | |
), | |
itemType = HomeTileItemType.HEADER | |
) | |
) | |
val actions = mutableListOf<QuickActionTile>() | |
actions.add( | |
QuickActionTile( | |
title = resourceProvider.getString(R.string.HOME_quick_action_focus_time), | |
iconResId = R.drawable.ic_quick_action_focus_time, | |
destination = QuickActionDestination.FOCUS_TIME, | |
color = resourceProvider.getColor(R.color.green) | |
) | |
) | |
actions.add( | |
QuickActionTile( | |
title = resourceProvider.getString(R.string.HOME_quick_action_web_mail), | |
iconResId = R.drawable.ic_home_quick_action_mail, | |
destination = QuickActionDestination.WEBMAIL, | |
color = resourceProvider.getColor(R.color.red) | |
) | |
) | |
actions.add( | |
QuickActionTile( | |
title = resourceProvider.getString(R.string.HOME_quick_action_exam_search), | |
iconResId = R.drawable.ic_home_quick_action_search, | |
destination = QuickActionDestination.SEARCH, | |
color = resourceProvider.getColor(R.color.cyan) | |
) | |
) | |
items.add( | |
HomeTileItem( | |
data = QuickActionCollection( | |
actions = actions | |
), | |
itemType = HomeTileItemType.QUICK_ACTION_TILE | |
) | |
) | |
return items | |
} | |
private fun generateAdministrationTile(): List<HomeTileItem> { | |
val items = mutableListOf<HomeTileItem>() | |
Timber.d("[generateAdministrationTile] Generating Administration Tile") | |
var headerAdded = false | |
val service = localDataSource.getSelectedService() | |
if (service != null) { | |
val entityTypes = service.entityTypes | |
// Taxes? | |
if (entityTypes.contains(EntityType.TAX)) { | |
if (!headerAdded) { | |
// header | |
items.add( | |
HomeTileItem( | |
data = HeaderTile( | |
title = resourceProvider.getString(R.string.HOME_administration) | |
), | |
itemType = HomeTileItemType.HEADER | |
) | |
) | |
headerAdded = true | |
} | |
// add the tax tile | |
var taxes = localDataSource.getTaxes() | |
val stringsToBold = mutableListOf<String>() | |
val todayMillis = System.currentTimeMillis() | |
taxes = taxes.sortedBy { it.dueDate } | |
val nextTax = taxes.singleOrNull { | |
!it.hasBeenPaid() && (it.dueDate ?: 0L) >= todayMillis | |
} | |
val taxShown: String | |
val taxTileContent = if (nextTax == null) { | |
taxShown = "none" | |
resourceProvider.getString(R.string.HOME_no_taxes_content) | |
} else { | |
taxShown = "due_fee" | |
val amount = nextTax.amount | |
stringsToBold.add(amount) | |
val formattedDate = if (nextTax.dueDate != null) { | |
UWDates.getDayMonthYearStringDate(nextTax.dueDate!!) | |
} else { | |
"N/A" | |
} | |
stringsToBold.add(formattedDate) | |
resourceProvider.getString(R.string.HOME_taxes_content, amount, formattedDate) | |
} | |
// generate tile | |
items.add( | |
HomeTileItem( | |
data = IconTitleTile( | |
title = resourceProvider.getString(R.string.HOME_tax_title), | |
subtitle = taxTileContent, | |
stringsToBold = stringsToBold, | |
color = resourceProvider.getColor(R.color.yellow), | |
iconResId = R.drawable.ic_icon_dollar, | |
destination = HomeDestination.MONEY, | |
taxShown = taxShown | |
), | |
itemType = HomeTileItemType.ICON_TITLE_TILE | |
) | |
) | |
} | |
// Tests? Applied or Available | |
if (entityTypes.contains(EntityType.APPLIEDTEST) || | |
entityTypes.contains(EntityType.AVAILABLETEST) || | |
entityTypes.contains(EntityType.AVAILABLEPARTIALTEST) | |
) { | |
if (!headerAdded) { | |
// header | |
items.add( | |
HomeTileItem( | |
data = HeaderTile( | |
title = resourceProvider.getString(R.string.HOME_administration) | |
), | |
itemType = HomeTileItemType.HEADER | |
) | |
) | |
headerAdded = true | |
} | |
val stringsToBold = mutableListOf<String>() | |
var stringContent = resourceProvider.getString(R.string.HOME_no_test) | |
// check for applied test | |
val appliedList = localDataSource.getAppliedTests() | |
// check for available tests | |
val availableList = localDataSource.getAvailableTests() | |
var segmentShown = "none" | |
var dayToTest: Int? = null | |
var numberOfTest: Int? = null | |
if (appliedList.isNotEmpty()) { | |
val today = Calendar.getInstance() | |
val firstTest = appliedList | |
.filter { it.getDate().after(today) } | |
.minByOrNull { it.testDate } | |
firstTest?.let { test -> | |
val formattedDate = UWDates.getDayMonthYearStringDate(test.testDate) | |
val testsNumber = appliedList.size.toString() | |
stringsToBold.add(testsNumber) | |
stringsToBold.add(formattedDate) | |
stringContent = resourceProvider.getString( | |
R.string.HOME_applied_test, | |
testsNumber, | |
formattedDate | |
) | |
// Tracking Stuff | |
segmentShown = "applied_preview" | |
val diff = test.getDate().timeInMillis - today.timeInMillis | |
dayToTest = (diff / (24 * 60 * 60 * 1000)).toInt() | |
numberOfTest = appliedList.size | |
} | |
} else { | |
if (availableList.isNotEmpty()) { | |
val firstTest = availableList.minByOrNull { it.applicationOpening ?: 0 } | |
firstTest?.let { test -> | |
val formattedDate = if (test.applicationOpening != null) { | |
UWDates.getDayMonthYearStringDate(test.applicationOpening!!) | |
} else { | |
"N/A" | |
} | |
val testsNumber = availableList.size.toString() | |
stringsToBold.add(testsNumber) | |
stringsToBold.add(formattedDate) | |
stringContent = resourceProvider.getString( | |
R.string.HOME_available_tests, | |
testsNumber, | |
formattedDate | |
) | |
segmentShown = "available_preview" | |
val today = Calendar.getInstance() | |
val diff = test.getDate().timeInMillis - today.timeInMillis | |
dayToTest = (diff / (24 * 60 * 60 * 1000)).toInt() | |
numberOfTest = availableList.size | |
} | |
} | |
} | |
// generate tile | |
items.add( | |
HomeTileItem( | |
data = IconTitleTile( | |
title = resourceProvider.getString(R.string.HOME_test_title), | |
subtitle = stringContent, | |
stringsToBold = stringsToBold, | |
color = resourceProvider.getColor(R.color.yellow), | |
iconResId = R.drawable.ic_icon_calendar, | |
destination = HomeDestination.TESTS, | |
testPreviewShown = segmentShown, | |
daysToTest = dayToTest, | |
numberOfTests = numberOfTest | |
), | |
itemType = HomeTileItemType.ICON_TITLE_TILE | |
) | |
) | |
} | |
// Webmail | |
if (service.wmEnabled) { | |
if (!headerAdded) { | |
// header | |
items.add( | |
HomeTileItem( | |
data = HeaderTile( | |
title = resourceProvider.getString(R.string.HOME_administration) | |
), | |
itemType = HomeTileItemType.HEADER | |
) | |
) | |
} | |
items.add( | |
HomeTileItem( | |
data = IconTitleTile( | |
title = resourceProvider.getString(R.string.WEBMAIL_title), | |
subtitle = resourceProvider.getString(R.string.HOME_webmail_content), | |
color = resourceProvider.getColor(R.color.yellow), | |
iconResId = R.drawable.ic_icon_mail, | |
destination = HomeDestination.WEBMAIL | |
), | |
itemType = HomeTileItemType.ICON_TITLE_TILE | |
) | |
) | |
} | |
} | |
return items | |
} | |
private fun getMean(): Double { | |
val averageType = localDataSource.getExamStatsAverageType() | |
val stats = localDataSource.getExamStats() | |
return if (averageType == ExamStatsAverageType.WEIGHTED) { | |
stats?.gpa ?: 0.0 | |
} else { | |
stats?.arithmeticMean ?: 0.0 | |
} | |
} | |
private fun identifyOnSegment() { | |
val user = coreRepository.currentUserDTO | |
val service = localDataSource.getSelectedService() | |
if (user != null) { | |
// Signup | |
// *** Segment identity | |
analyticsHelper.identifyEmail(accountUtils.getEmail()) | |
// Full Name | |
analyticsHelper.identify(AnalyticsIdentifyKeys.fullName, accountUtils.getFullName()) | |
// login | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.lastAccess, | |
System.currentTimeMillis().getSegmentFormattedTime() | |
) | |
// service choice | |
if (service != null) { | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.universitySelected, | |
service.serviceCode | |
) | |
analyticsHelper.identify(AnalyticsIdentifyKeys.country, service.country) | |
analyticsHelper.identify(AnalyticsIdentifyKeys.isFirstUser, false.toString()) | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.isFeesAvailable, | |
service.entityTypes.contains(EntityType.TAX).toString() | |
) | |
val testsAvailable = (service.entityTypes.contains(EntityType.APPLIEDTEST) || | |
service.entityTypes.contains(EntityType.AVAILABLETEST) || | |
service.entityTypes.contains(EntityType.AVAILABLEPARTIALTEST)) | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.isTestScheduleAvailable, | |
testsAvailable.toString() | |
) | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.isWebmailAvailable, | |
service.wmEnabled.toString() | |
) | |
localDataSource.getTranscript()?.let { transcriptResult -> | |
// class_passed_count | |
val examsDone = transcriptResult.exams.filter { it.hasGrade() }.size | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.classPassedCount, | |
examsDone.toString() | |
) | |
// class_todo_count | |
val examsTodo = transcriptResult.exams.filter { !it.hasGrade() }.size | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.classTodoCount, | |
examsTodo.toString() | |
) | |
} | |
localDataSource.getExamStats()?.let { examStats -> | |
examStats.gpaNormalized?.let { gpa -> | |
analyticsHelper.identify(AnalyticsIdentifyKeys.gpa, gpa.toString()) | |
} | |
// career_progression | |
val ectsTotal = examStats.ectsTotal() | |
val ectsDone = examStats.ectsDone() | |
val progress = if (ectsTotal == 0.0) { | |
0.0 | |
} else { | |
(ectsDone / ectsTotal) * 100.0 | |
} | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.careerProgression, | |
progress.toString() | |
) | |
// performance_percentile | |
analyticsHelper.identify( | |
AnalyticsIdentifyKeys.performancePercentile, | |
examStats.gpaPeersPerformance.toString() | |
) | |
} | |
} | |
viewModelScope.launch { | |
try { | |
// Just to update the list on the disk | |
coreRepository.refreshReviewList() | |
//TODO this only gets them from the network for the analytics - do we need it in this screen then? | |
coreRepository.getPomodoroUserStats() | |
} catch (e: Exception) { | |
// Fine to do nothing | |
} | |
} | |
} | |
} | |
fun isServiceEnabled(): Boolean { | |
localDataSource.getSelectedService()?.let { serviceDTO -> | |
return serviceDTO.enabled | |
} | |
return false | |
} | |
private suspend fun isServiceUnsupported(): Boolean { | |
val universityToShow = remoteConfigRepository.getConfig(FirebaseConfigParam.GetUniversityListToShow) | |
return if (universityToShow is FirebaseConfigResult.UniversityToShow) { | |
universityToShow.result?.let { uniToShow -> | |
val selectedService = localDataSource.getSelectedService()?.serviceCode | |
return uniToShow.servicesToShow.map { it.serviceCode }.contains(selectedService).not() | |
} ?: false | |
} else { | |
false | |
} | |
} | |
private suspend fun checkRatingSettings() { | |
when (val ratingSettings = remoteConfigRepository.getConfig(FirebaseConfigParam.RatingSettings)) { | |
is FirebaseConfigResult.RatingSettings -> { | |
Timber.d("Rating Settings - Remote Config : $ratingSettings") | |
isTimeForRating(ratingSettings) | |
} | |
else -> { | |
Timber.e("HomeViewModel.checkRatingSettings() - remoteConfigResult not expected: $ratingSettings") | |
} | |
} | |
} | |
fun getComingSoonUrl(): String? { | |
return localDataSource.getSelectedService()?.comingSoonUrl | |
} | |
private fun wasAppUpdated(): Boolean { | |
return settingsRepository.savedAppVersion.get() != lu.gian.uniwhere.core.BuildConfig.VERSION_CODE | |
} | |
fun setRatingDone() { | |
settingsRepository.ratingPopupShowed.set(true) | |
} | |
fun setWontReview() { | |
val actualWontReviewCount = settingsRepository.dontWantReview.get() +1 | |
settingsRepository.dontWantReview.set(actualWontReviewCount) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment