Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ android {
applicationId = "com.flux"
minSdk = 29
targetSdk = 37
versionCode = 19
versionName = "3.2.2"
versionCode = 20
versionName = "3.2.3"
}

dependenciesInfo {
Expand Down Expand Up @@ -89,6 +89,7 @@ dependencies {
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.hilt.common)
implementation(libs.androidx.material3)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.compose)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Flux"
android:theme="@style/Theme.Flux.Starting"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
104 changes: 95 additions & 9 deletions app/src/main/java/com/flux/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.flux

import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Build
import android.os.Bundle
import androidx.activity.compose.setContent
Expand All @@ -24,7 +27,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.rememberNavController
import com.flux.navigation.AppNavHost
import com.flux.navigation.Loader
import com.flux.other.Constants.Other
import com.flux.other.createNotificationChannel
import com.flux.ui.effects.ScreenEffect
Expand Down Expand Up @@ -54,13 +56,99 @@ class MainActivity : AppCompatActivity() {

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
createNotificationChannel(this)
// Splash screen condition
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { keepSplashScreen.value }

splashScreen.setKeepOnScreenCondition {
keepSplashScreen.value
}

// Animated exit transition for the system splash screen.
splashScreen.setOnExitAnimationListener { splashScreenView ->
val iconView = splashScreenView.iconView

// Initial state
iconView.alpha = 1f
iconView.scaleX = 1f
iconView.scaleY = 1f
iconView.translationY = 0f

// Subtle "settle" scale
val scaleX = ObjectAnimator.ofFloat(
iconView,
"scaleX",
1f,
1.08f,
0.82f
)

val scaleY = ObjectAnimator.ofFloat(
iconView,
"scaleY",
1f,
1.08f,
0.82f
)

// Move upward while leaving
val translationY = ObjectAnimator.ofFloat(
iconView,
"translationY",
0f,
-20f,
-90f
)

// Fade slightly later than the movement begins
val fadeOut = ObjectAnimator.ofFloat(
iconView,
"alpha",
1f,
1f,
0f
)

AnimatorSet().apply {

playTogether(
scaleX,
scaleY,
translationY,
fadeOut
)

duration = 550L

// Smooth deceleration rather than AnticipateInterpolator.
interpolator = android.view.animation.PathInterpolator(
0.2f,
0f,
0f,
1f
)

addListener(object : Animator.AnimatorListener {

override fun onAnimationStart(animation: Animator) = Unit

override fun onAnimationEnd(animation: Animator) {
splashScreenView.remove()
}

override fun onAnimationCancel(animation: Animator) {
splashScreenView.remove()
}

override fun onAnimationRepeat(animation: Animator) = Unit
})

start()
}
}

enableEdgeToEdge()

setContent {
val snackBarHostState = remember { SnackbarHostState() }

Expand Down Expand Up @@ -89,7 +177,7 @@ class MainActivity : AppCompatActivity() {
val progressBoardState by progressBoardViewModel.state.collectAsStateWithLifecycle()
val rootChangeState by settingsViewModel.rootChangeState.collectAsStateWithLifecycle()

// Stop splash screen when settings are loaded
// Keep system splash on screen until settings are actually loaded
LaunchedEffect(settings.isLoading) { keepSplashScreen.value = settings.isLoading }

HandleSideEffects(
Expand All @@ -98,8 +186,8 @@ class MainActivity : AppCompatActivity() {
settingsViewModel.effect
)

if (!settings.isLoading) {
FluxTheme(settings, settingsViewModel::onEvent) {
FluxTheme(settings, settingsViewModel::onEvent) {
if (!settings.isLoading) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.surfaceContainerLow
Expand Down Expand Up @@ -135,8 +223,6 @@ class MainActivity : AppCompatActivity() {
)
}
}
} else {
Loader()
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/java/com/flux/navigation/NavRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ val NotesScreens =
states.settings.data.isLintValid,
states.settings.data.isLineNumbersVisible,
states.settings.data.startWithReadView,
states.notesState.allNotes.find { it.notesId == notesId } ?: NotesModel(workspaceId = workspaceId),
states.notesState.allNotes.find { it.notesId == notesId } ?: NotesModel(workspaceId = workspaceId, notesId = notesId),
states.settings.data.storageRootUri,
states.labelState.allLabels.filter { it.workspaceId==workspaceId },
viewModel.settingsViewModel,
Expand Down Expand Up @@ -146,7 +146,7 @@ val HabitScreens =
NavRoutes.NewHabit.route + "/{workspaceId}" + "/{habitId}" to { navController, habitId, workspaceId, states, viewModel ->
NewHabit(
navController,
states.habitState.allHabits.find { it.id == habitId } ?: HabitModel(workspaceId=workspaceId),
states.habitState.allHabits.find { it.id == habitId } ?: HabitModel(workspaceId=workspaceId, id = habitId),
states.settings,
viewModel.habitViewModel::onEvent
)
Expand All @@ -173,7 +173,7 @@ val TodoScreens =
NewTodoList(
navController,
states.settings.data.is24HourFormat,
states.todoState.allLists.find { it.id == listId } ?: TodoModel(workspaceId = workspaceId),
states.todoState.allLists.find { it.id == listId } ?: TodoModel(workspaceId = workspaceId, id = listId),
workspaceId,
viewModel.todoViewModel::onEvent
)
Expand All @@ -187,7 +187,7 @@ val JournalScreens =
navController,
states.workspaceState.allWorkspaces,
workspaceId,
states.journalState.data.find { it.journalId == journalId } ?: JournalModel(workspaceId = workspaceId, dateTime = journalDateTime),
states.journalState.data.find { it.journalId == journalId } ?: JournalModel(journalId = journalId, workspaceId = workspaceId, dateTime = journalDateTime),
states.journalState.outline,
states.journalState.textState,
states.settings.data.fontNumber,
Expand Down Expand Up @@ -266,7 +266,7 @@ val EventScreens =
NavRoutes.NewEvent.route + "/{workspaceId}" + "/{eventId}" + "/{eventDate}" to { navController, states, viewModels, eventId, workspaceId, _, eventDate ->
NewEvent(
navController,
states.eventState.allEvent.find { it.id == eventId } ?: EventModel(workspaceId = workspaceId, startDateTime = eventDate),
states.eventState.allEvent.find { it.id == eventId } ?: EventModel(workspaceId = workspaceId, startDateTime = eventDate, id = eventId),
states.settings,
viewModels.eventViewModel::onEvent
)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/flux/other/BackupManger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit

class BackupManager(
class BackupManager (
private val workManager: WorkManager
) {
fun scheduleBackup(frequency: BackupFrequency) {
Expand Down
42 changes: 32 additions & 10 deletions app/src/main/java/com/flux/other/BackupWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package com.flux.other

import android.content.Context
import androidx.core.net.toUri
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.flux.R
import com.flux.data.database.FluxBackup
import com.flux.data.model.SettingsModel
import com.flux.di.DataModule
import com.flux.other.crypto.BackupCredentialsStore
import com.flux.other.crypto.BackupEncryptor
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.OutputStreamWriter

enum class BackupFrequency(val days: Int, val textRes: Int) {
NEVER(0, R.string.never),
Expand All @@ -20,11 +24,21 @@ enum class BackupFrequency(val days: Int, val textRes: Int) {
MONTHLY(30, R.string.monthly);
}

class BackupWorker(
appContext: Context,
workerParams: WorkerParameters
@HiltWorker
class BackupWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
private val credentialsStore: BackupCredentialsStore,
private val backupEncryptor: BackupEncryptor
) : CoroutineWorker(appContext, workerParams) {

private val backupJson = Json {
ignoreUnknownKeys = true
coerceInputValues = true
encodeDefaults = true
classDiscriminator = "type"
}

override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
val context = applicationContext
Expand All @@ -41,8 +55,9 @@ class BackupWorker(
val eventDao = DataModule.provideEventDao(fluxDatabase)
val eventInstanceDao = DataModule.provideEventInstanceDao(fluxDatabase)
val progressBoardDao = DataModule.provideProgressBoardDao(fluxDatabase)

val settings = settingsDao.loadSetting()
val rootUri = settings?.storageRootUri?.toUri()?: "".toUri()
val rootUri = settings?.storageRootUri?.toUri() ?: "".toUri()

val baseDir = getOrCreateDirectory(context, rootUri, Constants.File.FLUX)
val backupDir = baseDir?.let { dir ->
Expand All @@ -60,20 +75,27 @@ class BackupWorker(
labels = labelDao.getAll(),
events = eventDao.loadAllEvents(),
eventInstances = eventInstanceDao.getAll(),
settings = settingsDao.loadSetting()?: SettingsModel(),
settings = settingsDao.loadSetting() ?: SettingsModel(),
progressBoardItems = progressBoardDao.getAllBoardItems()
)
val json = Json.encodeToString(FluxBackup.serializer(), backup)
val json = backupJson.encodeToString(FluxBackup.serializer(), backup)

val password = credentialsStore.getPassword()
val bytes = if (password != null) {
backupEncryptor.encrypt(json.toByteArray(Charsets.UTF_8), password)
.also { password.fill('\u0000') }
} else {
json.toByteArray(Charsets.UTF_8) // no password set → plaintext, unchanged legacy behavior
}

val fileName = "${System.currentTimeMillis()}.json"
val file = dir.createFile("application/json", fileName)

file?.let { docFile ->
context.contentResolver.openOutputStream(docFile.uri)
?.use { outputStream ->
OutputStreamWriter(outputStream).use { writer ->
writer.write(json)
}
outputStream.write(bytes)
outputStream.flush()
}
}
}
Expand Down
Loading
Loading