Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2c5ef16
style: redesign notes search, directory dialog UI, directories screen
IntFxZen May 11, 2026
57577ce
fix: ktlintcheck
IntFxZen May 11, 2026
5c8822e
fix: longMethod in app search field
IntFxZen May 12, 2026
1efd85d
fix: long method in directories screen
IntFxZen May 12, 2026
e77ca7f
fix: edit directories screen
IntFxZen May 12, 2026
e07b38c
fix: CI
IntFxZen May 12, 2026
24c0085
style: change color when note is selected
IntFxZen May 12, 2026
d2dcd2c
style: some improvments
IntFxZen May 13, 2026
c9b581e
style: add empty state to directories screen
IntFxZen May 13, 2026
310754b
style: fix move to folder dialog
IntFxZen May 13, 2026
a8b3a82
style: change outline text field to basic text field
IntFxZen May 13, 2026
6e6ac0f
style: create folder verification
IntFxZen May 14, 2026
1ec3684
style: add fullscreen preview
IntFxZen May 14, 2026
ab674cd
style: select several photos
IntFxZen May 15, 2026
95044ba
style: add verification to text field in editor screen
IntFxZen May 15, 2026
1426725
feat: implement search functionality
IntFxZen May 15, 2026
654f794
style: add ai tags and summary
IntFxZen May 16, 2026
a633f2c
style: change all icons type to rounded
IntFxZen May 16, 2026
324b256
feat: add favorites
IntFxZen May 16, 2026
2b9af7f
style: some improvments
IntFxZen May 16, 2026
da5d96d
fix bugs with ui
IntFxZen May 16, 2026
8efe4a6
fix: insert text in fields
IntFxZen May 16, 2026
e5dd9c8
fix errors
IntFxZen May 16, 2026
e88b768
feat: add auth
IntFxZen May 17, 2026
96bac8a
fix: auth
IntFxZen May 17, 2026
f8f9365
fix: add logout
IntFxZen May 17, 2026
5f4ebab
fix: some improvments
IntFxZen May 17, 2026
5e1029b
style: some improvements
IntFxZen May 17, 2026
0bebabb
fix ci
IntFxZen May 17, 2026
f063de3
fix: auth local files
IntFxZen May 17, 2026
224d773
fix: google-services.json
IntFxZen May 17, 2026
93fccd3
fix: delete debugging files
IntFxZen May 17, 2026
e34b91d
fix: restore gitignore
IntFxZen May 17, 2026
809fe89
fix: generate lockfiles
IntFxZen May 17, 2026
28ba0a8
fix lint
IntFxZen May 17, 2026
8ae089d
resolve conflicts
IntFxZen May 17, 2026
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
16 changes: 16 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
Expand Down Expand Up @@ -71,6 +72,15 @@ kotlin {
}
}

configure<DetektExtension> {
config.setFrom(
files(
rootProject.file("detekt.yml"),
layout.projectDirectory.file("detekt.yml"),
),
)
}

dependencies {
implementation(project(":domain"))
implementation(project(":data"))
Expand All @@ -80,11 +90,14 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
implementation(libs.koin.android)
implementation(libs.koin.androidx.compose)
implementation(libs.androidx.datastore.preferences)
implementation(libs.coil.compose)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand All @@ -97,5 +110,8 @@ dependencies {
implementation(libs.androidx.compose.material.icons.extended)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
implementation(libs.firebase.auth)
implementation(libs.kotlinx.coroutines.play.services)
implementation(libs.play.services.auth)
implementation(libs.koin.workmanager)
}
14 changes: 14 additions & 0 deletions app/detekt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# UI (app) module: Jetpack Compose entry points often exceed default complexity limits.
complexity:
LongParameterList:
active: false
LongMethod:
active: false
TooManyFunctions:
active: false
CyclomaticComplexMethod:
active: false

style:
ReturnCount:
active: false
54 changes: 34 additions & 20 deletions app/google-services.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
{
"project_info": {
"project_number": "123456789",
"project_id": "mock-id"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:123456789:android:mock",
"android_client_info": {
"package_name": "com.itlab.notes"
}
},
"api_key": [
{
"project_info": {
"project_number": "123456789",
"project_id": "mock-id"
},
"client": [
{
"current_key": "mock-key"
"client_info": {
"mobilesdk_app_id": "1:123456789:android:mock",
"android_client_info": {
"package_name": "com.itlab.notes"
}
},
"oauth_client": [
{
"client_id": "mock-id",
"client_type": 1,
"android_info": {
"package_name": "com.itlab.notes",
"certificate_hash": "mock-hash"
}
},
{
"client_id": "mock-id",
"client_type": 3
}
],
"api_key": [
{
"current_key": "mock-key"
}
],
"services": {}
}
],
"services": {}
}
],
"configuration_version": "1"
],
"configuration_version": "1"
}
685 changes: 313 additions & 372 deletions app/gradle.lockfile

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="Notes"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Notes">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.itlab.notes.di
package com.itlab

import com.itlab.domain.usecase.folderusecase.CreateFolderUseCase
import com.itlab.domain.usecase.folderusecase.DeleteFolderUseCase
Expand All @@ -7,18 +7,33 @@ import com.itlab.domain.usecase.folderusecase.ObserveFoldersUseCase
import com.itlab.domain.usecase.folderusecase.UpdateFolderUseCase
import com.itlab.domain.usecase.noteusecase.CreateNoteUseCase
import com.itlab.domain.usecase.noteusecase.DeleteNoteUseCase
import com.itlab.domain.usecase.noteusecase.GetAllFavoritesUseCase
import com.itlab.domain.usecase.noteusecase.GetNoteUseCase
import com.itlab.domain.usecase.noteusecase.GetUserIdUseCase
import com.itlab.domain.usecase.noteusecase.MoveNoteToFolderUseCase
import com.itlab.domain.usecase.noteusecase.ObserveNotesByFolderUseCase
import com.itlab.domain.usecase.noteusecase.ObserveNotesUseCase
import com.itlab.domain.usecase.noteusecase.SearchNotesUseCase
import com.itlab.domain.usecase.noteusecase.SwitchFavoriteUseCase
import com.itlab.domain.usecase.noteusecase.UpdateNoteUseCase
import com.itlab.domain.usecase.noteusecase.ValidateDuplicateNoteTitleUseCase
import com.itlab.notes.auth.AppSessionPreferences
import com.itlab.notes.auth.ClearLocalDataOnSignOut
import com.itlab.notes.onboarding.OnboardingPreferences
import com.itlab.notes.onboarding.OnboardingViewModel
import com.itlab.notes.ui.NotesUseCases
import com.itlab.notes.ui.NotesViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import com.itlab.notes.ui.auth.AuthViewModel
import org.koin.android.ext.koin.androidApplication
import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module

val appModule =
module {
single { OnboardingPreferences(androidApplication()) }
single { AppSessionPreferences(androidApplication()) }
factory { ValidateDuplicateNoteTitleUseCase(get()) }
factory { CreateNoteUseCase(get()) }
factory { CreateFolderUseCase(get()) }
factory { DeleteFolderUseCase(get(), get()) }
Expand All @@ -31,6 +46,20 @@ val appModule =
factory { MoveNoteToFolderUseCase(get(), get()) }
factory { ObserveNotesUseCase(get()) }
factory { GetUserIdUseCase(get()) }
factory { SearchNotesUseCase(get()) }
factory { SwitchFavoriteUseCase(get()) }
factory { GetAllFavoritesUseCase(get()) }
factory { GetNoteUseCase(get()) }
factory { UpdateFolderUseCase(get()) }
factory { GetFolderUseCase(get()) }
factory {
ClearLocalDataOnSignOut(
observeNotesUseCase = get(),
deleteNoteUseCase = get(),
observeFoldersUseCase = get(),
deleteFolderUseCase = get(),
)
}
factory {
NotesUseCases(
createFolderUseCase = get(),
Expand All @@ -45,12 +74,21 @@ val appModule =
moveNoteToFolderUseCase = get(),
observeNotesUseCase = get(),
getUserIdUseCase = get(),
searchNotesUseCase = get(),
switchFavoriteUseCase = get(),
getAllFavoritesUseCase = get(),
getNoteUseCase = get(),
)
}

viewModelOf(::NotesViewModel)
viewModelOf(::OnboardingViewModel)
viewModel {
NotesViewModel(
useCases = get(),
AuthViewModel(
firebaseAuth = get(),
app = androidApplication(),
appSessionPreferences = get(),
clearLocalDataOnSignOut = get(),
)
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/itlab/notes/NotesApplication.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.itlab.notes

import android.app.Application
import com.itlab.appModule
import com.itlab.data.di.dataModule
import com.itlab.notes.di.appModule
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.workmanager.koin.workManagerFactory
import org.koin.core.context.startKoin
Expand Down
33 changes: 33 additions & 0 deletions app/src/main/java/com/itlab/notes/auth/AppSessionPreferences.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.itlab.notes.auth

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

internal val Context.appSessionDataStore: DataStore<Preferences> by preferencesDataStore(
name = "app_session_preferences",
)

class AppSessionPreferences(
private val context: Context,
) {
val continueOffline: Flow<Boolean> =
context.appSessionDataStore.data.map { prefs ->
prefs[CONTINUE_OFFLINE] ?: false
}

suspend fun setContinueOffline(enabled: Boolean) {
context.appSessionDataStore.edit { prefs ->
prefs[CONTINUE_OFFLINE] = enabled
}
}

private companion object {
val CONTINUE_OFFLINE = booleanPreferencesKey("continue_offline")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.itlab.notes.auth

import com.itlab.domain.usecase.folderusecase.DeleteFolderUseCase
import com.itlab.domain.usecase.folderusecase.ObserveFoldersUseCase
import com.itlab.domain.usecase.noteusecase.DeleteNoteUseCase
import com.itlab.domain.usecase.noteusecase.ObserveNotesUseCase
import kotlinx.coroutines.flow.first

/** Removes all local notes and folders when the user signs out (app-layer only). */
class ClearLocalDataOnSignOut(
private val observeNotesUseCase: ObserveNotesUseCase,
private val deleteNoteUseCase: DeleteNoteUseCase,
private val observeFoldersUseCase: ObserveFoldersUseCase,
private val deleteFolderUseCase: DeleteFolderUseCase,
) {
suspend operator fun invoke() {
observeNotesUseCase().first().forEach { note ->
deleteNoteUseCase(note.id)
}
observeFoldersUseCase().first().forEach { folder ->
deleteFolderUseCase(folder.id)
}
}
}
68 changes: 68 additions & 0 deletions app/src/main/java/com/itlab/notes/media/ImageRegionLuminance.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.itlab.notes.media

import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.get

/**
* Estimates whether the top-end region of an image is light (for adaptive icon contrast).
*/
object ImageRegionLuminance {
private const val LIGHT_REGION_THRESHOLD = 0.55f

fun isTopEndRegionLight(
drawable: Drawable,
sampleMaxSize: Int = 96,
): Boolean {
val width = drawable.intrinsicWidth.takeIf { it > 0 } ?: sampleMaxSize
val height = drawable.intrinsicHeight.takeIf { it > 0 } ?: sampleMaxSize
val scale = minOf(1f, sampleMaxSize.toFloat() / maxOf(width, height))
val targetWidth = (width * scale).toInt().coerceAtLeast(1)
val targetHeight = (height * scale).toInt().coerceAtLeast(1)
val bitmap = drawable.toBitmap(targetWidth, targetHeight)
return try {
isTopEndRegionLight(bitmap)
} finally {
if (drawable !is BitmapDrawable || drawable.bitmap !== bitmap) {
bitmap.recycle()
}
}
}

private fun isTopEndRegionLight(bitmap: Bitmap): Boolean {
val width = bitmap.width
val height = bitmap.height
if (width == 0 || height == 0) return true

val startX = (width * 0.5f).toInt().coerceIn(0, width - 1)
val endY = (height * 0.5f).toInt().coerceAtLeast(1)
val step = maxOf(1, minOf(width, height) / 10)

var luminanceSum = 0.0
var count = 0
var x = startX
while (x < width) {
var y = 0
while (y < endY) {
val pixel = bitmap[x, y]
val composeColor =
Color(
red = android.graphics.Color.red(pixel) / 255f,
green = android.graphics.Color.green(pixel) / 255f,
blue = android.graphics.Color.blue(pixel) / 255f,
)
luminanceSum += composeColor.luminance()
count++
y += step
}
x += step
}

if (count == 0) return true
return (luminanceSum / count) > LIGHT_REGION_THRESHOLD
}
}
21 changes: 21 additions & 0 deletions app/src/main/java/com/itlab/notes/media/NoteMediaDisplay.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.itlab.notes.media

import com.itlab.domain.model.ContentItem
import com.itlab.domain.model.DataSource
import java.io.File

fun List<ContentItem>.withoutTextItems(): List<ContentItem> = filterNot { it is ContentItem.Text }

fun List<ContentItem>.imageAttachments(): List<ContentItem.Image> = filterIsInstance<ContentItem.Image>()

fun DataSource.toCoilModel(): Any? {
localPath
?.takeIf { it.isNotBlank() }
?.let { path ->
val file = File(path)
if (file.isFile && file.canRead() && file.length() > 0L) {
return file
}
}
return remoteUrl?.takeIf { it.isNotBlank() }
}
Loading
Loading