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
6 changes: 4 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 = 16
versionName = "3.1.10"
versionCode = 17
versionName = "3.2.0"
}

dependenciesInfo {
Expand Down Expand Up @@ -138,4 +138,6 @@ dependencies {

// draggable list
implementation(libs.reorderable)

implementation(libs.androidx.security.crypto)
}
5 changes: 4 additions & 1 deletion app/src/main/java/com/flux/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.flux.other.createNotificationChannel
import com.flux.ui.effects.ScreenEffect
import com.flux.ui.state.States
import com.flux.ui.theme.FluxTheme
import com.flux.ui.viewModel.BackupSettingsViewModel
import com.flux.ui.viewModel.BackupViewModel
import com.flux.ui.viewModel.EventViewModel
import com.flux.ui.viewModel.HabitViewModel
Expand Down Expand Up @@ -74,6 +75,7 @@ class MainActivity : AppCompatActivity() {
val backupViewModel: BackupViewModel = hiltViewModel()
val labelViewModel: LabelViewModel = hiltViewModel()
val progressBoardViewModel: ProgressBoardViewModel = hiltViewModel()
val backupSettingsViewModel: BackupSettingsViewModel = hiltViewModel()

// States
val settings by settingsViewModel.state.collectAsState()
Expand Down Expand Up @@ -114,7 +116,8 @@ class MainActivity : AppCompatActivity() {
settingsViewModel,
backupViewModel,
labelViewModel,
progressBoardViewModel
progressBoardViewModel,
backupSettingsViewModel
),
states = States(
notesState,
Expand Down
18 changes: 5 additions & 13 deletions app/src/main/java/com/flux/data/database/FluxDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -411,18 +411,14 @@ val MIGRATION_10_11 = object : Migration(10, 11) {
}

val MIGRATION_11_12 = object : Migration(11, 12) {

private val TAG = "Migration_11_12"
private val OLD_TABLE = "WorkspaceModel"
private val TMP_TABLE = "WorkspaceModel_new"

override fun migrate(db: SupportSQLiteDatabase) {
Log.i(TAG, "Starting migration 11 -> 12 (passKey -> hashed passKeyHash)")

db.safeExec("ALTER TABLE NotesModel ADD COLUMN socialLinks TEXT NOT NULL DEFAULT '[]'")
db.safeExec("ALTER TABLE JournalModel ADD COLUMN socialLinks TEXT NOT NULL DEFAULT '[]'")
recreateTableWithHashedColumn(db)
rehashExistingPasswords(db)

Log.i(TAG, "Migration 11 -> 12 finished")
}

/** Step 1: recreate WorkspaceModel with passKeyHash instead of passKey, copying all rows as-is. */
Expand Down Expand Up @@ -455,8 +451,6 @@ val MIGRATION_11_12 = object : Migration(11, 12) {

db.safeExec("DROP TABLE IF EXISTS `$OLD_TABLE`")
db.safeExec("ALTER TABLE `$TMP_TABLE` RENAME TO `$OLD_TABLE`")

Log.d(TAG, "WorkspaceModel table recreated with passKeyHash column")
}

/**
Expand Down Expand Up @@ -487,7 +481,6 @@ val MIGRATION_11_12 = object : Migration(11, 12) {

try {
if (PasswordHasher.isHashed(currentValue)) {
Log.d(TAG, "Workspace $workspaceId already hashed, skipping")
alreadyHashed++
continue
}
Expand All @@ -501,22 +494,21 @@ val MIGRATION_11_12 = object : Migration(11, 12) {
arrayOf(hashed, workspaceId)
)
migrated++
Log.d(TAG, "Workspace $workspaceId passkey hashed successfully")
} catch (rowError: Exception) {
failed++
Log.e(
TAG,
"Migration_11_12",
"Failed to hash passkey for workspace $workspaceId, leaving value untouched",
rowError
)
}
}
} catch (e: Exception) {
Log.e(TAG, "Critical error while iterating WorkspaceModel rows for hashing", e)
Log.e("Migration_11_12", "Critical error while iterating WorkspaceModel rows for hashing", e)
} finally {
cursor?.close()
Log.i(
TAG,
"Migration_11_12",
"Passkey hashing summary — total: $total, migrated: $migrated, " +
"alreadyHashed: $alreadyHashed, failed: $failed"
)
Expand Down
16 changes: 15 additions & 1 deletion app/src/main/java/com/flux/data/model/Converter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,21 @@ class Converter {
json.decodeFromString(value)
} catch (
_: Exception) {
HabitConfig.Simple // fallback (critical)
HabitConfig.Simple
}
}

@TypeConverter
fun socialLinksToJson(value: List<SocialModel>): String {
return json.encodeToString(value)
}

@TypeConverter
fun jsonToSocialLinks(value: String): List<SocialModel> {
return if (value.isBlank()) {
emptyList()
} else {
json.decodeFromString(value)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/flux/data/model/JournalModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ data class JournalModel(
val workspaceId: String = "",
val text: String = "",
val dateTime: Long = System.currentTimeMillis(),
val labels: List<String> = emptyList()
val labels: List<String> = emptyList(),
val socialLinks: List<SocialModel> = emptyList(),
)

fun JournalModel.writtenOnDate(date: LocalDate): Boolean {
Expand Down
158 changes: 158 additions & 0 deletions app/src/main/java/com/flux/data/model/NotesModel.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.flux.data.model

import androidx.compose.runtime.Composable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.flux.R
import java.util.UUID
import kotlinx.serialization.Serializable

Expand All @@ -14,6 +16,162 @@ data class NotesModel(
val title: String = "",
val description: String = "",
val isPinned: Boolean = false,
val socialLinks: List<SocialModel> = emptyList(),
val labels: List<String> = emptyList(),
val lastEdited: Long = System.currentTimeMillis()
)

@Serializable
data class SocialModel(
val socialId: String = UUID.randomUUID().toString(),
val notesId: String = "",
val workspaceId: String = "",
val category: Int = 0,
val title: String = "",
val link: String = "",
)

data class SocialCategory(
val name: String,
val icon: Int,
val containerColor: Long,
val contentColor: Long,
)

@Composable
fun getSocialCategory(): List<SocialCategory> {
return listOf(
SocialCategory(
name = "Facebook",
icon = R.drawable.ic_facebook,
containerColor = 0xFF1877F2,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Instagram",
icon = R.drawable.ic_instagram,
containerColor = 0xFFFFFFFF,
contentColor = 0xFF181717
),
SocialCategory(
name = "Twitter",
icon = R.drawable.ic_twitter,
containerColor = 0xFF000000,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Threads",
icon = R.drawable.ic_threads,
containerColor = 0xFF000000,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "WhatsApp",
icon = R.drawable.ic_whatsapp,
containerColor = 0xFF25D366,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Telegram",
icon = R.drawable.ic_telegram,
containerColor = 0xFF0088CC,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Discord",
icon = R.drawable.ic_discord,
containerColor = 0xFF5865F2,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Snapchat",
icon = R.drawable.ic_snapchat,
containerColor = 0xFFFFFC00,
contentColor = 0xFF181717
),
SocialCategory(
name = "TikTok",
icon = R.drawable.ic_tiktok,
containerColor = 0xFF000000,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "YouTube",
icon = R.drawable.ic_youtube,
containerColor = 0xFFFF0000,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "LinkedIn",
icon = R.drawable.ic_linkedin,
containerColor = 0xFF0A66C2,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Pinterest",
icon = R.drawable.ic_pinterest,
containerColor = 0xFFBD081C,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Reddit",
icon = R.drawable.ic_reddit,
containerColor = 0xFFFF4500,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Twitch",
icon = R.drawable.ic_twitch,
containerColor = 0xFF9146FF,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "WeChat",
icon = R.drawable.ic_wechat,
containerColor = 0xFF07C160,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Skype",
icon = R.drawable.ic_skype,
containerColor = 0xFF00AFF0,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Slack",
icon = R.drawable.ic_slack,
containerColor = 0xFF4A154B,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "GitHub",
icon = R.drawable.ic_github,
containerColor = 0xFFFFFFFF,
contentColor = 0xFF181717
),
SocialCategory(
name = "ChatGPT",
icon = R.drawable.ic_chatgpt,
containerColor = 0xFF10A37F,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Gemini",
icon = R.drawable.ic_gemini,
containerColor = 0x4285F4,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "AI",
icon = R.drawable.ic_ai,
containerColor = 0xFF7C3AED,
contentColor = 0xFFFFFFFF
),
SocialCategory(
name = "Other",
icon = R.drawable.ic_other,
containerColor = 0xFFFFFFF,
contentColor = 0xFF181717
)
)
}
29 changes: 29 additions & 0 deletions app/src/main/java/com/flux/di/BackupCryptoModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.flux.di

import com.flux.other.crypto.AesGcmBackupEncryptor
import com.flux.other.crypto.BackupCredentialsStore
import com.flux.other.crypto.BackupEncryptor
import com.flux.other.crypto.EncryptedPrefsBackupCredentialsStore
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class BackupCryptoModule {

@Binds
@Singleton
abstract fun bindBackupCredentialsStore(
impl: EncryptedPrefsBackupCredentialsStore
): BackupCredentialsStore

companion object {
@Provides
@Singleton
fun provideBackupEncryptor(): BackupEncryptor = AesGcmBackupEncryptor()
}
}
7 changes: 6 additions & 1 deletion app/src/main/java/com/flux/navigation/NavRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.flux.ui.screens.labels.EditLabels
import com.flux.ui.screens.notes.NoteDetails
import com.flux.ui.screens.search.SearchScreen
import com.flux.ui.screens.settings.About
import com.flux.ui.screens.settings.Changelog
import com.flux.ui.screens.settings.Contact
import com.flux.ui.screens.settings.Customize
import com.flux.ui.screens.settings.Data
Expand Down Expand Up @@ -65,6 +66,7 @@ sealed class NavRoutes(val route: String) {
data object Theme : NavRoutes("settings/customize/theme")
data object Languages : NavRoutes("settings/language")
data object About : NavRoutes("settings/about")
data object Changelog : NavRoutes("settings/about/changelog")
data object Contact : NavRoutes("settings/contact")
data object Backup : NavRoutes("setting/backup")
data object Editor : NavRoutes("setting/editor")
Expand Down Expand Up @@ -227,7 +229,7 @@ val SettingsScreens =
Contact(navController, states.settings.data.cornerRadius)
},
NavRoutes.Backup.route to { navController, snackbarHostState, states, viewModels ->
Data(navController, states.settings.data.cornerRadius, states.settings, snackbarHostState, viewModels.backupViewModel, viewModels.settingsViewModel::onEvent)
Data(navController, states.settings.data.cornerRadius, states.settings, snackbarHostState, viewModels.backupViewModel, viewModels.backupSettingsViewModel, viewModels.settingsViewModel::onEvent)
},
NavRoutes.Editor.route to { navController, _, states, viewModels ->
Editor(navController, states.settings, viewModels.settingsViewModel::onEvent)
Expand All @@ -240,6 +242,9 @@ val SettingsScreens =
},
NavRoutes.NotesPreview.route to { navController, _, states, viewModels ->
NotesPreviewSetting(navController, states.settings, viewModels.settingsViewModel::onEvent)
},
NavRoutes.Changelog.route to { navController, _, _, _ ->
Changelog(navController)
}
)

Expand Down
Loading
Loading