From 06f4396e82cfa89bd23798607d909a4ca5e1b873 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:09:43 +0000 Subject: [PATCH 001/790] chore(deps): Bump peter-evans/create-pull-request from 7.0.6 to 7.0.8 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.6 to 7.0.8. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v7.0.6...v7.0.8) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/generate_baselineprofile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_baselineprofile.yml b/.github/workflows/generate_baselineprofile.yml index 75c07eb6..9248243a 100644 --- a/.github/workflows/generate_baselineprofile.yml +++ b/.github/workflows/generate_baselineprofile.yml @@ -49,7 +49,7 @@ jobs: -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1 - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.6 + uses: peter-evans/create-pull-request@v7.0.8 with: commit-message: Update baseline committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> From f269166527e8f12f04c45451be82dd5ecfc893c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:47:41 +0000 Subject: [PATCH 002/790] Chore(deps): Bump androidTools from 31.9.2 to 31.10.1 Bumps `androidTools` from 31.9.2 to 31.10.1. Updates `com.android.tools.lint:lint-api` from 31.9.2 to 31.10.1 Updates `com.android.tools.lint:lint-checks` from 31.9.2 to 31.10.1 Updates `com.android.tools.lint:lint-tests` from 31.9.2 to 31.10.1 Updates `com.android.tools:common` from 31.9.2 to 31.10.1 --- updated-dependencies: - dependency-name: com.android.tools.lint:lint-api dependency-version: 31.10.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.tools.lint:lint-checks dependency-version: 31.10.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.tools.lint:lint-tests dependency-version: 31.10.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.tools:common dependency-version: 31.10.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 869b07ff..8f5bd99a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ accompanist = "0.37.3" androidDesugarJdkLibs = "2.1.5" androidGradlePlugin = "8.8.1" -androidTools = "31.9.2" +androidTools = "31.10.1" androidxActivity = "1.10.1" androidxAppCompat = "1.7.1" androidxBrowser = "1.8.0" From 94d5512f962127bf2ea03c77b60bb560ec278854 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 07:58:10 +0100 Subject: [PATCH 003/790] Feat: Add custom lint checks This commit introduces a new `lint` module with custom lint checks for the Notepad project. - **DesignSystemDetector**: Ensures that Compose Material APIs are not used directly and instead, the equivalents from the `core-designsystem` module are used. - Checks for incorrect usage of composable functions like `MaterialTheme`, `Button`, `NavigationBar`, etc., and suggests their `Note` prefixed counterparts (e.g., `NoteTheme`, `NoteButton`). - Checks for incorrect usage of receiver objects like `Icons` and suggests `NoteIcons`. - **TestMethodNameDetector**: Enforces naming conventions for test methods. - Warns if a test method name starts with "test". - For Android instrumented tests, warns if the method name does not follow the `given_when_then` or `when_then` format. - **NotepadIssueRegistry**: Registers the custom lint checks. - Adds the `lint` module to `settings.gradle.kts`. - Includes necessary dependencies and configurations in `lint/build.gradle.kts`. --- lint/.gitignore | 1 + lint/build.gradle.kts | 44 +++++ .../mshdabiola/lint/NotepadIssueRegistry.kt | 27 +++ .../mshdabiola/lint/TestMethodNameDetector.kt | 111 ++++++++++++ .../lint/designsystem/DesignSystemDetector.kt | 102 +++++++++++ ...ndroid.tools.lint.client.api.IssueRegistry | 17 ++ .../lint/TestMethodNameDetectorTest.kt | 123 +++++++++++++ .../designsystem/DesignSystemDetectorTest.kt | 164 ++++++++++++++++++ settings.gradle.kts | 8 +- 9 files changed, 591 insertions(+), 6 deletions(-) create mode 100644 lint/.gitignore create mode 100644 lint/build.gradle.kts create mode 100644 lint/src/main/kotlin/com/mshdabiola/lint/NotepadIssueRegistry.kt create mode 100644 lint/src/main/kotlin/com/mshdabiola/lint/TestMethodNameDetector.kt create mode 100644 lint/src/main/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetector.kt create mode 100644 lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry create mode 100644 lint/src/test/kotlin/com/mshdabiola/lint/TestMethodNameDetectorTest.kt create mode 100644 lint/src/test/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetectorTest.kt diff --git a/lint/.gitignore b/lint/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/lint/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lint/build.gradle.kts b/lint/build.gradle.kts new file mode 100644 index 00000000..e042513b --- /dev/null +++ b/lint/build.gradle.kts @@ -0,0 +1,44 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + `java-library` + kotlin("jvm") + id("mshdabiola.android.lint") +} + +java { + // Up to Java 11 APIs are available through desugaring + // https://developer.android.com/studio/write/java11-minimal-support-table + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_21 + } +} + +dependencies { + compileOnly(libs.kotlin.stdlib) + compileOnly(libs.lint.api) + testImplementation(libs.kotlin.test) + testImplementation(libs.lint.checks) + testImplementation(libs.lint.tests) +} diff --git a/lint/src/main/kotlin/com/mshdabiola/lint/NotepadIssueRegistry.kt b/lint/src/main/kotlin/com/mshdabiola/lint/NotepadIssueRegistry.kt new file mode 100644 index 00000000..7dce6240 --- /dev/null +++ b/lint/src/main/kotlin/com/mshdabiola/lint/NotepadIssueRegistry.kt @@ -0,0 +1,27 @@ + + +package com.mshdabiola.lint + +import com.android.tools.lint.client.api.IssueRegistry +import com.android.tools.lint.client.api.Vendor +import com.android.tools.lint.detector.api.CURRENT_API +import com.mshdabiola.lint.designsystem.DesignSystemDetector + +class NotepadIssueRegistry : IssueRegistry() { + + override val issues = listOf( + DesignSystemDetector.ISSUE, + TestMethodNameDetector.FORMAT, + TestMethodNameDetector.PREFIX, + ) + + override val api: Int = CURRENT_API + + override val minApi: Int = 12 + + override val vendor: Vendor = Vendor( + vendorName = "Notepad", + feedbackUrl = "https://github.com/mshdabiola/notepad/issues", + contact = "https://github.com/mshdabiola/notepad", + ) +} diff --git a/lint/src/main/kotlin/com/mshdabiola/lint/TestMethodNameDetector.kt b/lint/src/main/kotlin/com/mshdabiola/lint/TestMethodNameDetector.kt new file mode 100644 index 00000000..6b53250a --- /dev/null +++ b/lint/src/main/kotlin/com/mshdabiola/lint/TestMethodNameDetector.kt @@ -0,0 +1,111 @@ + +package com.mshdabiola.lint + +import com.android.tools.lint.detector.api.AnnotationInfo +import com.android.tools.lint.detector.api.AnnotationUsageInfo +import com.android.tools.lint.detector.api.Category.Companion.TESTING +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.LintFix +import com.android.tools.lint.detector.api.Scope.JAVA_FILE +import com.android.tools.lint.detector.api.Scope.TEST_SOURCES +import com.android.tools.lint.detector.api.Severity.WARNING +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.android.tools.lint.detector.api.TextFormat.RAW +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UElement +import java.util.EnumSet +import kotlin.io.path.Path + +/** + * A detector that checks for common patterns in naming the test methods: + * - [detectPrefix] removes unnecessary "test" prefix in all unit test. + * - [detectFormat] Checks the `given_when_then` format of Android instrumented tests (backticks are not supported). + */ +class TestMethodNameDetector : Detector(), SourceCodeScanner { + + override fun applicableAnnotations() = listOf("org.junit.Test") + + override fun visitAnnotationUsage( + context: JavaContext, + element: UElement, + annotationInfo: AnnotationInfo, + usageInfo: AnnotationUsageInfo, + ) { + val method = usageInfo.referenced as? PsiMethod ?: return + + method.detectPrefix(context, usageInfo) + method.detectFormat(context, usageInfo) + } + + private fun JavaContext.isAndroidTest() = Path("androidTest") in file.toPath() + + private fun PsiMethod.detectPrefix( + context: JavaContext, + usageInfo: AnnotationUsageInfo, + ) { + if (!name.startsWith("test")) return + context.report( + issue = PREFIX, + scope = usageInfo.usage, + location = context.getNameLocation(this), + message = PREFIX.getBriefDescription(RAW), + quickfixData = LintFix.create() + .name("Remove prefix") + .replace().pattern("""test[\s_]*""") + .with("") + .autoFix() + .build(), + ) + } + + private fun PsiMethod.detectFormat( + context: JavaContext, + usageInfo: AnnotationUsageInfo, + ) { + if (!context.isAndroidTest()) return + if ("""[^\W_]+(_[^\W_]+){1,2}""".toRegex().matches(name)) return + context.report( + issue = FORMAT, + scope = usageInfo.usage, + location = context.getNameLocation(this), + message = FORMAT.getBriefDescription(RAW), + ) + } + + companion object { + + private fun issue( + id: String, + briefDescription: String, + explanation: String, + ): Issue = Issue.create( + id = id, + briefDescription = briefDescription, + explanation = explanation, + category = TESTING, + priority = 5, + severity = WARNING, + implementation = Implementation( + TestMethodNameDetector::class.java, + EnumSet.of(JAVA_FILE, TEST_SOURCES), + ), + ) + + @JvmField + val PREFIX: Issue = issue( + id = "TestMethodPrefix", + briefDescription = "Test method starts with `test`", + explanation = "Test method should not start with `test`.", + ) + + @JvmField + val FORMAT: Issue = issue( + id = "TestMethodFormat", + briefDescription = "Test method does not follow the `given_when_then` or `when_then` format", + explanation = "Test method should follow the `given_when_then` or `when_then` format.", + ) + } +} diff --git a/lint/src/main/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetector.kt b/lint/src/main/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetector.kt new file mode 100644 index 00000000..b72e49a2 --- /dev/null +++ b/lint/src/main/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetector.kt @@ -0,0 +1,102 @@ + +package com.mshdabiola.lint.designsystem + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UQualifiedReferenceExpression + +/** + * A detector that checks for incorrect usages of Compose Material APIs over equivalents in + * the Now in Android design system module. + */ +class DesignSystemDetector : Detector(), Detector.UastScanner { + + override fun getApplicableUastTypes(): List> = listOf( + UCallExpression::class.java, + UQualifiedReferenceExpression::class.java, + ) + + override fun createUastHandler(context: JavaContext): UElementHandler = + object : UElementHandler() { + override fun visitCallExpression(node: UCallExpression) { + val name = node.methodName ?: return + val preferredName = METHOD_NAMES[name] ?: return + reportIssue(context, node, name, preferredName) + } + + override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) { + val name = node.receiver.asRenderString() + val preferredName = RECEIVER_NAMES[name] ?: return + reportIssue(context, node, name, preferredName) + } + } + + companion object { + @JvmField + val ISSUE: Issue = Issue.create( + id = "DesignSystem", + briefDescription = "Design system", + explanation = "This check highlights calls in code that use Compose Material " + + "composables instead of equivalents from the Now in Android design system " + + "module.", + category = Category.CUSTOM_LINT_CHECKS, + priority = 7, + severity = Severity.ERROR, + implementation = Implementation( + DesignSystemDetector::class.java, + Scope.JAVA_FILE_SCOPE, + ), + ) + + // Unfortunately :lint is a Java module and thus can't depend on the :core-designsystem + // Android module, so we can't use composable function references (eg. ::Button.name) + // instead of hardcoded names. + val METHOD_NAMES = mapOf( + "MaterialTheme" to "NoteTheme", + "Button" to "NoteButton", + "OutlinedButton" to "NoteOutlinedButton", + "TextButton" to "NoteTextButton", + "FilterChip" to "NoteFilterChip", + "ElevatedFilterChip" to "NoteFilterChip", + "NavigationBar" to "NoteNavigationBar", + "NavigationBarItem" to "NoteNavigationBarItem", + "NavigationRail" to "NoteNavigationRail", + "NavigationRailItem" to "NoteNavigationRailItem", + "TabRow" to "NoteTabRow", + "Tab" to "NoteTab", + "IconToggleButton" to "NoteIconToggleButton", + "FilledIconToggleButton" to "NoteIconToggleButton", + "FilledTonalIconToggleButton" to "NoteIconToggleButton", + "OutlinedIconToggleButton" to "NoteIconToggleButton", + "CenterAlignedTopAppBar" to "NoteTopAppBar", + "SmallTopAppBar" to "NoteTopAppBar", + "MediumTopAppBar" to "NoteTopAppBar", + "LargeTopAppBar" to "NoteTopAppBar", + ) + val RECEIVER_NAMES = mapOf( + "Icons" to "NoteIcons", + ) + + fun reportIssue( + context: JavaContext, + node: UElement, + name: String, + preferredName: String, + ) { + context.report( + ISSUE, + node, + context.getLocation(node), + "Using $name instead of $preferredName", + ) + } + } +} diff --git a/lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry new file mode 100644 index 00000000..10b27f46 --- /dev/null +++ b/lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry @@ -0,0 +1,17 @@ +# +# Copyright 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +com.mshdabiola.lint.NotepadIssueRegistry diff --git a/lint/src/test/kotlin/com/mshdabiola/lint/TestMethodNameDetectorTest.kt b/lint/src/test/kotlin/com/mshdabiola/lint/TestMethodNameDetectorTest.kt new file mode 100644 index 00000000..2a3da1a8 --- /dev/null +++ b/lint/src/test/kotlin/com/mshdabiola/lint/TestMethodNameDetectorTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mshdabiola.lint + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin +import com.android.tools.lint.checks.infrastructure.TestLintTask.lint +import com.mshdabiola.lint.TestMethodNameDetector.Companion.FORMAT +import com.mshdabiola.lint.TestMethodNameDetector.Companion.PREFIX +import org.junit.Test + +class TestMethodNameDetectorTest { + + @Test + fun `detect prefix`() { + lint().issues(PREFIX) + .files( + JUNIT_TEST_STUB, + kotlin( + """ + import org.junit.Test + class Test { + @Test + fun foo() = Unit + @Test + fun test_foo() = Unit + @Test + fun `test foo`() = Unit + } + """, + ).indented(), + ) + .run() + .expect( + """ + src/Test.kt:6: Warning: Test method starts with test [TestMethodPrefix] + fun test_foo() = Unit + ~~~~~~~~ + src/Test.kt:8: Warning: Test method starts with test [TestMethodPrefix] + fun `test foo`() = Unit + ~~~~~~~~~~ + 0 errors, 2 warnings + """.trimIndent(), + ) + .expectFixDiffs( + """ + Autofix for src/Test.kt line 6: Remove prefix: + @@ -6 +6 + - fun test_foo() = Unit + + fun foo() = Unit + Autofix for src/Test.kt line 8: Remove prefix: + @@ -8 +8 + - fun `test foo`() = Unit + + fun `foo`() = Unit + """.trimIndent(), + ) + } + + @Test + fun `detect format`() { + lint().issues(FORMAT) + .files( + JUNIT_TEST_STUB, + kotlin( + "src/androidTest/com/example/Test.kt", + """ + import org.junit.Test + class Test { + @Test + fun when_then() = Unit + @Test + fun given_when_then() = Unit + + @Test + fun foo() = Unit + @Test + fun foo_bar_baz_qux() = Unit + @Test + fun `foo bar baz`() = Unit + } + """, + ).indented(), + ) + .run() + .expect( + """ + src/androidTest/com/example/Test.kt:9: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat] + fun foo() = Unit + ~~~ + src/androidTest/com/example/Test.kt:11: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat] + fun foo_bar_baz_qux() = Unit + ~~~~~~~~~~~~~~~ + src/androidTest/com/example/Test.kt:13: Warning: Test method does not follow the given_when_then or when_then format [TestMethodFormat] + fun `foo bar baz`() = Unit + ~~~~~~~~~~~~~ + 0 errors, 3 warnings + """.trimIndent(), + ) + } + + private companion object { + private val JUNIT_TEST_STUB: TestFile = kotlin( + """ + package org.junit + annotation class Test + """, + ).indented() + } +} diff --git a/lint/src/test/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetectorTest.kt b/lint/src/test/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetectorTest.kt new file mode 100644 index 00000000..d8889c39 --- /dev/null +++ b/lint/src/test/kotlin/com/mshdabiola/lint/designsystem/DesignSystemDetectorTest.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mshdabiola.lint.designsystem + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin +import com.android.tools.lint.checks.infrastructure.TestLintTask.lint +import com.mshdabiola.lint.designsystem.DesignSystemDetector.Companion.ISSUE +import com.mshdabiola.lint.designsystem.DesignSystemDetector.Companion.METHOD_NAMES +import com.mshdabiola.lint.designsystem.DesignSystemDetector.Companion.RECEIVER_NAMES +import org.junit.Test + +class DesignSystemDetectorTest { + + @Test + fun `detect replacements of Composable`() { + lint() + .issues(ISSUE) + .allowMissingSdk() + .files( + COMPOSABLE_STUB, + STUBS, + @Suppress("LintImplTrimIndent") + kotlin( + """ + |import androidx.compose.runtime.Composable + | + |@Composable + |fun App() { + ${METHOD_NAMES.keys.joinToString("\n") { "| $it()" }} + |} + """.trimMargin(), + ).indented(), + ) + .run() + .expect( + """ + src/test.kt:5: Error: Using MaterialTheme instead of NoteTheme [DesignSystem] + MaterialTheme() + ~~~~~~~~~~~~~~~ + src/test.kt:6: Error: Using Button instead of NoteButton [DesignSystem] + Button() + ~~~~~~~~ + src/test.kt:7: Error: Using OutlinedButton instead of NoteOutlinedButton [DesignSystem] + OutlinedButton() + ~~~~~~~~~~~~~~~~ + src/test.kt:8: Error: Using TextButton instead of NoteTextButton [DesignSystem] + TextButton() + ~~~~~~~~~~~~ + src/test.kt:9: Error: Using FilterChip instead of NoteFilterChip [DesignSystem] + FilterChip() + ~~~~~~~~~~~~ + src/test.kt:10: Error: Using ElevatedFilterChip instead of NoteFilterChip [DesignSystem] + ElevatedFilterChip() + ~~~~~~~~~~~~~~~~~~~~ + src/test.kt:11: Error: Using NavigationBar instead of NoteNavigationBar [DesignSystem] + NavigationBar() + ~~~~~~~~~~~~~~~ + src/test.kt:12: Error: Using NavigationBarItem instead of NoteNavigationBarItem [DesignSystem] + NavigationBarItem() + ~~~~~~~~~~~~~~~~~~~ + src/test.kt:13: Error: Using NavigationRail instead of NoteNavigationRail [DesignSystem] + NavigationRail() + ~~~~~~~~~~~~~~~~ + src/test.kt:14: Error: Using NavigationRailItem instead of NoteNavigationRailItem [DesignSystem] + NavigationRailItem() + ~~~~~~~~~~~~~~~~~~~~ + src/test.kt:15: Error: Using TabRow instead of NoteTabRow [DesignSystem] + TabRow() + ~~~~~~~~ + src/test.kt:16: Error: Using Tab instead of NoteTab [DesignSystem] + Tab() + ~~~~~ + src/test.kt:17: Error: Using IconToggleButton instead of NoteIconToggleButton [DesignSystem] + IconToggleButton() + ~~~~~~~~~~~~~~~~~~ + src/test.kt:18: Error: Using FilledIconToggleButton instead of NoteIconToggleButton [DesignSystem] + FilledIconToggleButton() + ~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:19: Error: Using FilledTonalIconToggleButton instead of NoteIconToggleButton [DesignSystem] + FilledTonalIconToggleButton() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:20: Error: Using OutlinedIconToggleButton instead of NoteIconToggleButton [DesignSystem] + OutlinedIconToggleButton() + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:21: Error: Using CenterAlignedTopAppBar instead of NoteTopAppBar [DesignSystem] + CenterAlignedTopAppBar() + ~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:22: Error: Using SmallTopAppBar instead of NoteTopAppBar [DesignSystem] + SmallTopAppBar() + ~~~~~~~~~~~~~~~~ + src/test.kt:23: Error: Using MediumTopAppBar instead of NoteTopAppBar [DesignSystem] + MediumTopAppBar() + ~~~~~~~~~~~~~~~~~ + src/test.kt:24: Error: Using LargeTopAppBar instead of NoteTopAppBar [DesignSystem] + LargeTopAppBar() + ~~~~~~~~~~~~~~~~ + 20 errors, 0 warnings + """.trimIndent(), + ) + } + + @Test + fun `detect replacements of Receiver`() { + lint() + .issues(ISSUE) + .allowMissingSdk() + .files( + COMPOSABLE_STUB, + STUBS, + @Suppress("LintImplTrimIndent") + kotlin( + """ + |fun main() { + ${RECEIVER_NAMES.keys.joinToString("\n") { "| $it.toString()" }} + |} + """.trimMargin(), + ).indented(), + ) + .run() + .expect( + """ + src/test.kt:2: Error: Using Icons instead of NoteIcons [DesignSystem] + Icons.toString() + ~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.trimIndent(), + ) + } + + private companion object { + + private val COMPOSABLE_STUB: TestFile = kotlin( + """ + package androidx.compose.runtime + annotation class Composable + """.trimIndent(), + ).indented() + + private val STUBS: TestFile = kotlin( + """ + |import androidx.compose.runtime.Composable + | + ${METHOD_NAMES.keys.joinToString("\n") { "|@Composable fun $it() = {}" }} + ${RECEIVER_NAMES.keys.joinToString("\n") { "|object $it" }} + | + """.trimMargin(), + ).indented() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 15eb772d..bf13bec6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,18 +25,12 @@ include(":modules:datastore") include(":modules:model") include(":modules:common") include(":modules:testing") -//include(":modules:screenshot-testing") include(":modules:database") -//include(":modules:network") include(":modules:analytics") include(":modules:designsystem") include(":modules:domain") include(":modules:ui") -//include(":modules:worker") -//include(":feature:mainscreen") -//include(":feature:editScreen") include(":feature:labelscreen") -//include(":feature:searchscreen") include(":feature:selectlabelscreen") include(":feature:gallery") include(":feature:drawing") @@ -46,6 +40,8 @@ include(":ui-test-hilt-manifest") include(":feature:main") include(":feature:detail") include(":feature:setting") +include(":lint") + From e505bfb30e0cae051107cd2a021ac9175c91fe30 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 07:59:01 +0100 Subject: [PATCH 004/790] Refactor: Rename Theme and add new components This commit renames `SkTheme` to `NoteTheme` for clarity and introduces new UI components and icons. - In `Theme.kt`, the composable `SkTheme` has been renamed to `NoteTheme`. - New icons `Alarm` and `Repeat` have been added to `NoteIcon.kt`. - A new composable `NoteTextButton` has been created in `Button.kt`. - The `Button` composable in `Button.kt` has been updated to remove the explicit setting of `containerColor`. - In `designsystem/build.gradle.kts`, `lintPublish(projects.lint)` has been uncommented, enabling lint checks for this module. --- modules/designsystem/build.gradle.kts | 2 +- .../designsystem/component/Button.kt | 33 ++++++++++++++++--- .../mshdabiola/designsystem/icon/NoteIcon.kt | 4 +++ .../mshdabiola/designsystem/theme/Theme.kt | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/modules/designsystem/build.gradle.kts b/modules/designsystem/build.gradle.kts index 202179bf..601ec948 100644 --- a/modules/designsystem/build.gradle.kts +++ b/modules/designsystem/build.gradle.kts @@ -16,7 +16,7 @@ android { } dependencies { - // lintPublish(projects.lint) + lintPublish(projects.lint) api(libs.androidx.compose.foundation) api(libs.androidx.compose.foundation.layout) diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Button.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Button.kt index 4ac98472..88e83397 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Button.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Button.kt @@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -28,9 +28,9 @@ fun NoteButton( onClick = onClick, modifier = modifier, enabled = enabled, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.onBackground, - ), +// colors = ButtonDefaults.buttonColors( +// containerColor = MaterialTheme.colorScheme.onBackground, +// ), contentPadding = contentPadding, content = content, ) @@ -61,6 +61,31 @@ fun NoteButton( } } +@Composable +fun NoteTextButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + leadingIcon: @Composable (() -> Unit)? = null, + text: @Composable () -> Unit, +) { + TextButton( + onClick = onClick, + modifier = modifier, + enabled = enabled, + contentPadding = if (leadingIcon != null) { + ButtonDefaults.ButtonWithIconContentPadding + } else { + ButtonDefaults.ContentPadding + }, + ) { + NoteButtonContent( + text = text, + leadingIcon = leadingIcon, + ) + } +} + @Composable private fun NoteButtonContent( text: @Composable () -> Unit, diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt index d1290cc0..0ac203a5 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt @@ -9,6 +9,7 @@ import androidx.compose.material.icons.filled.PushPin import androidx.compose.material.icons.outlined.AccessTime import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.AddBox +import androidx.compose.material.icons.outlined.Alarm import androidx.compose.material.icons.outlined.Archive import androidx.compose.material.icons.outlined.Brush import androidx.compose.material.icons.outlined.Cancel @@ -37,6 +38,7 @@ import androidx.compose.material.icons.outlined.PauseCircle import androidx.compose.material.icons.outlined.PhotoCamera import androidx.compose.material.icons.outlined.PlayCircle import androidx.compose.material.icons.outlined.PushPin +import androidx.compose.material.icons.outlined.Repeat import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Share @@ -47,6 +49,8 @@ import com.mshdabiola.designsystem.R object NoteIcon { + val Alarm = Icons.Outlined.Alarm + val Repeat = Icons.Outlined.Repeat val Edit = Icons.Outlined.Edit val ViewAgenda = Icons.Outlined.ViewAgenda val Menu = Icons.Outlined.Menu diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/theme/Theme.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/theme/Theme.kt index 868ad758..55b0738a 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/theme/Theme.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/theme/Theme.kt @@ -25,7 +25,7 @@ var extendedColorScheme: ExtendedColorScheme = extendedLight @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun SkTheme( +fun NoteTheme( darkTheme: Boolean = isSystemInDarkTheme(), androidTheme: Boolean = false, disableDynamicTheming: Boolean = true, From 6e27a30b94b607d2af36dd7a19b4c03d7f3e918f Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 07:59:49 +0100 Subject: [PATCH 005/790] Refactor: Update theme name in ScreenshotHelper This commit updates the theme name used in the `ScreenshotHelper.kt` file. - Changed `SkTheme` to `NoteTheme` in the `captureRoboImage` and `ThemedScreenshot` functions. --- .../main/java/com/mshdabiola/testing/util/ScreenshotHelper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/util/ScreenshotHelper.kt b/modules/testing/src/main/java/com/mshdabiola/testing/util/ScreenshotHelper.kt index 09e063fc..698473f4 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/util/ScreenshotHelper.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/util/ScreenshotHelper.kt @@ -20,7 +20,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.mshdabiola.designsystem.theme.SkTheme +import com.mshdabiola.designsystem.theme.NoteTheme /** * Takes six screenshots combining light/dark and default/Android themes and whether dynamic color @@ -87,7 +87,7 @@ fun Capture( description: String = "", content: @Composable () -> Unit, ) { - SkTheme( + NoteTheme( androidTheme = androidTheme, darkTheme = darkMode, disableDynamicTheming = !dynamicTheming, From 022094773e211f172394cff64b7f0a3fca40942c Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:00:13 +0100 Subject: [PATCH 006/790] Refactor: Standardize UI components and icons This commit updates various UI elements to use standardized components and icons from the design system. - Replaced Material `Button` and `TextButton` with `NoteButton` and `NoteTextButton` in `TimeDialog.kt`, `NewDialog.kt`, and `DateDialog.kt`. - Replaced Material `CircularProgressIndicator` with `NoteLoadingWheel` in `WaitingUi.kt`. - Updated icons in `MainActions.kt`, `RemainderCard.kt`, and `ColorDialog.kt` to use icons from `NoteIcon`. - Changed `MaterialTheme` to `NoteTheme` in `FlowLayout.kt` preview. - Removed unused date/time formatting extension functions from `TimeExtention.kt`. --- .../kotlin/com/mshdabiola/ui/ColorDialog.kt | 9 +-- .../kotlin/com/mshdabiola/ui/DateDialog.kt | 8 +- .../kotlin/com/mshdabiola/ui/FlowLayout.kt | 4 +- .../kotlin/com/mshdabiola/ui/MainActions.kt | 8 +- .../kotlin/com/mshdabiola/ui/NewDialog.kt | 10 +-- .../kotlin/com/mshdabiola/ui/RemainderCard.kt | 6 +- .../kotlin/com/mshdabiola/ui/TimeDialog.kt | 8 +- .../kotlin/com/mshdabiola/ui/TimeExtention.kt | 73 ------------------- .../kotlin/com/mshdabiola/ui/WaitingUi.kt | 4 +- 9 files changed, 26 insertions(+), 104 deletions(-) diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/ColorDialog.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/ColorDialog.kt index 166296a6..324747bd 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/ColorDialog.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/ColorDialog.kt @@ -10,9 +10,6 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Done -import androidx.compose.material.icons.outlined.FormatColorReset import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -63,14 +60,14 @@ fun ColorDialog( ) { if (-1 == currentColor) { Icon( - imageVector = Icons.Default.Done, + imageVector = NoteIcon.Done, contentDescription = "done", tint = Color.Blue, modifier = Modifier.padding(4.dp), ) } else { Icon( - imageVector = Icons.Outlined.FormatColorReset, + imageVector = NoteIcon.FormatColorReset, contentDescription = "done", tint = Color.Gray, modifier = Modifier.padding(4.dp), @@ -97,7 +94,7 @@ fun ColorDialog( ) { if (index == currentColor) { Icon( - imageVector = Icons.Default.Done, + imageVector = NoteIcon.Done, contentDescription = "done", tint = Color.Blue, modifier = Modifier.padding(4.dp), diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/DateDialog.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/DateDialog.kt index d24049dc..22fac7ff 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/DateDialog.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/DateDialog.kt @@ -1,7 +1,6 @@ package com.mshdabiola.ui import androidx.compose.animation.AnimatedVisibility -import androidx.compose.material3.Button import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.DatePickerDialog @@ -9,9 +8,10 @@ import androidx.compose.material3.DatePickerState import androidx.compose.material3.DisplayMode import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import com.mshdabiola.designsystem.component.NoteButton +import com.mshdabiola.designsystem.component.NoteTextButton import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -32,7 +32,7 @@ fun DateDialog( DatePickerDialog( onDismissRequest = onDismissRequest, confirmButton = { - Button(onClick = { + NoteButton(onClick = { onSetDate() onDismissRequest() }) { @@ -40,7 +40,7 @@ fun DateDialog( } }, dismissButton = { - TextButton(onClick = onDismissRequest) { + NoteTextButton(onClick = onDismissRequest) { Text(text = "Cancel") } }, diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/FlowLayout.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/FlowLayout.kt index 3a9ecdf1..ab297574 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/FlowLayout.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/FlowLayout.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -18,6 +17,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.theme.NoteTheme import kotlin.math.max fun flowLayoutMeasurePolicy() = MeasurePolicy { measurables, constraints -> @@ -174,7 +174,7 @@ fun FlowLayout( @Preview(showBackground = true) @Composable private fun PreviewFlowRow() { - MaterialTheme { + NoteTheme { Surface { FlowLayout( modifier = Modifier.padding(8.dp), diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt index 474f016a..b9d202e4 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt @@ -17,9 +17,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Image -import androidx.compose.material.icons.outlined.PhotoCamera import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -31,6 +28,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.icon.NoteIcon @Composable fun AudioDialog( @@ -184,7 +182,7 @@ fun ImageDialog( verticalAlignment = Alignment.CenterVertically, ) { Icon( - imageVector = Icons.Outlined.PhotoCamera, + imageVector = NoteIcon.PhotoCamera, contentDescription = "take image", ) Spacer(modifier = Modifier.width(8.dp)) @@ -201,7 +199,7 @@ fun ImageDialog( verticalAlignment = Alignment.CenterVertically, ) { Icon( - imageVector = Icons.Outlined.Image, + imageVector = NoteIcon.Image, contentDescription = "take phone", ) Spacer(modifier = Modifier.width(8.dp)) diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/NewDialog.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/NewDialog.kt index 34f39ab2..6de205fd 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/NewDialog.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/NewDialog.kt @@ -6,13 +6,11 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -22,6 +20,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.component.NoteButton +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.ui.state.DateDialogUiData import com.mshdabiola.ui.state.DateListUiState import kotlinx.collections.immutable.toImmutableList @@ -65,7 +65,7 @@ fun NotificationDialogNew( } }, confirmButton = { - Button( + NoteButton( onClick = { onSetAlarm() onDismissRequest() @@ -78,7 +78,7 @@ fun NotificationDialogNew( dismissButton = { Row { if (dateDialogUiData.isEdit) { - TextButton(onClick = { + NoteTextButton(onClick = { onDismissRequest() onDeleteAlarm() }) { @@ -86,7 +86,7 @@ fun NotificationDialogNew( } Spacer(modifier = Modifier.width(8.dp)) } - TextButton(onClick = { onDismissRequest() }) { + NoteTextButton(onClick = { onDismissRequest() }) { Text(text = "Cancel") } } diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/RemainderCard.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/RemainderCard.kt index 7a9e3578..1e0d8100 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/RemainderCard.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/RemainderCard.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Repeat import androidx.compose.material.icons.outlined.Alarm import androidx.compose.material3.Icon @@ -24,6 +23,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.icon.NoteIcon import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.minus @@ -51,14 +51,14 @@ fun ReminderCard( if (interval > 0) { Icon( modifier = Modifier.size(16.dp), - imageVector = Icons.Default.Repeat, + imageVector = NoteIcon.Repeat, contentDescription = "", ) Spacer(modifier = Modifier.width(2.dp)) } else { Icon( modifier = Modifier.size(16.dp), - imageVector = Icons.Outlined.Alarm, + imageVector = NoteIcon.Alarm, contentDescription = "", ) Spacer(modifier = Modifier.width(2.dp)) diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeDialog.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeDialog.kt index 33a85c72..4b09090d 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeDialog.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeDialog.kt @@ -1,15 +1,15 @@ package com.mshdabiola.ui import androidx.compose.animation.AnimatedVisibility -import androidx.compose.material3.Button import androidx.compose.material3.DatePickerDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TimePicker import androidx.compose.material3.TimePickerState import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import com.mshdabiola.designsystem.component.NoteButton +import com.mshdabiola.designsystem.component.NoteTextButton @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -23,7 +23,7 @@ fun TimeDialog( DatePickerDialog( onDismissRequest = onDismissRequest, confirmButton = { - Button(onClick = { + NoteButton(onClick = { onSetTime() onDismissRequest() @@ -32,7 +32,7 @@ fun TimeDialog( } }, dismissButton = { - TextButton(onClick = onDismissRequest) { + NoteTextButton(onClick = onDismissRequest) { Text(text = "Cancel") } }, diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeExtention.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeExtention.kt index 4d9b901e..2cc3aac8 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeExtention.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeExtention.kt @@ -1,80 +1,7 @@ package com.mshdabiola.ui -// package com.mshdabiola.designsystem.component -// -// import kotlinx.datetime.Clock -// import kotlinx.datetime.DateTimeUnit -// import kotlinx.datetime.Instant -// import kotlinx.datetime.TimeZone -// import kotlinx.datetime.minus -// import kotlinx.datetime.plus -// import kotlinx.datetime.toLocalDateTime -// import kotlinx.datetime.todayIn -// -// fun Long.toTimeString(isCurr: Boolean = false): String { -// val instant = -// Instant.fromEpochMilliseconds(this) -// -// val dateTime = instant.toLocalDateTime( -// if (isCurr) TimeZone.currentSystemDefault() else TimeZone.UTC, -// ) -// val hour = dateTime.hour % 12L -// val a = if (dateTime.hour > 11) "PM" else "AM" -// return "%02d : %02d %s".format(hour, dateTime.minute, a) -// } -// fun Long.toTime(): String { val hour = this / 60000 val minute = this / 1000 % 60 return "%02d : %02d".format(hour, minute) } -// -// fun Long.toDateString(): String { -// val instant = -// Instant.fromEpochMilliseconds(this) -// val today = Clock.System.todayIn(TimeZone.UTC) -// val tomorrow = today.plus(1, DateTimeUnit.DAY) -// val yesterday = today.minus(DateTimeUnit.DAY) -// val dateTime = instant.toLocalDateTime(TimeZone.UTC) -// -// val datestring = when (dateTime.date) { -// today -> "Today" -// tomorrow -> "Tomorrow" -// yesterday -> "Yesterday" -// else -> { -// "${ -// dateTime.month.name.substring(0..2).lowercase().replaceFirstChar { it.uppercase() } -// } ${dateTime.dayOfMonth}" -// } -// } -// -// return datestring -// } -// -// fun Long.toTimeAndDate(): String { -// val instant = -// Instant.fromEpochMilliseconds(this) -// val today = Clock.System.todayIn(TimeZone.currentSystemDefault()) -// -// val dateTime = instant.toLocalDateTime(TimeZone.currentSystemDefault()) -// -// return when { -// today == dateTime.date -> { -// val hour = dateTime.hour % 12L -// val a = if (dateTime.hour > 11) "PM" else "AM" -// "%02d : %02d %s".format(hour, dateTime.minute, a) -// } -// -// today.minus(dateTime.date).years > 0 -> { -// "${ -// dateTime.month.name.substring(0..2).lowercase().replaceFirstChar { it.uppercase() } -// } ${dateTime.dayOfMonth} ${dateTime.year}" -// } -// -// else -> { -// "${ -// dateTime.month.name.substring(0..2).lowercase().replaceFirstChar { it.uppercase() } -// } ${dateTime.dayOfMonth}" -// } -// } -// } diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/WaitingUi.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/WaitingUi.kt index 78a2c0a5..d8789ef1 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/WaitingUi.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/WaitingUi.kt @@ -3,11 +3,11 @@ package com.mshdabiola.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import com.mshdabiola.designsystem.component.NoteLoadingWheel @Composable fun Waiting(modifier: Modifier = Modifier) { @@ -16,7 +16,7 @@ fun Waiting(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - CircularProgressIndicator() + NoteLoadingWheel("") Text("Loading...") } } From c4e938b82dd604f1b6350f9194fc887b5ffdeb9b Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:00:27 +0100 Subject: [PATCH 007/790] Refactor: Remove unused UI components This commit removes the following unused UI composable functions and classes: - `NoteficationDialog.kt`: This file contained composables related to displaying a dialog for setting and editing notifications, including `NotificationDialog`, `TimeContent`, `TimeDropbox`, `DateDropbox`, and `RepeatDropbox`. - `NotifySneackerUi.kt`: This file contained the `NotifySnacker` composable, which was responsible for displaying snackbar notifications. - `TimeZoneBroadcastReceiver.kt`: This file contained the `TimeZoneBroadcastReceiver` class, used to listen for system time zone changes. --- .../com/mshdabiola/ui/NoteficationDialog.kt | 376 ------------------ .../com/mshdabiola/ui/NotifySneackerUi.kt | 32 -- .../ui/TimeZoneBroadcastReceiver.kt | 38 -- 3 files changed, 446 deletions(-) delete mode 100644 modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteficationDialog.kt delete mode 100644 modules/ui/src/main/kotlin/com/mshdabiola/ui/NotifySneackerUi.kt delete mode 100644 modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeZoneBroadcastReceiver.kt diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteficationDialog.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteficationDialog.kt deleted file mode 100644 index d7304c9a..00000000 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/NoteficationDialog.kt +++ /dev/null @@ -1,376 +0,0 @@ -package com.mshdabiola.ui // package com.mshdabiola.designsystem.component -// -// import androidx.compose.animation.AnimatedVisibility -// import androidx.compose.foundation.layout.Column -// import androidx.compose.foundation.layout.Row -// import androidx.compose.foundation.layout.Spacer -// import androidx.compose.foundation.layout.width -// import androidx.compose.material3.AlertDialog -// import androidx.compose.material3.Button -// import androidx.compose.material3.DropdownMenuItem -// import androidx.compose.material3.ExperimentalMaterial3Api -// import androidx.compose.material3.ExposedDropdownMenuBox -// import androidx.compose.material3.ExposedDropdownMenuDefaults -// import androidx.compose.material3.Text -// import androidx.compose.material3.TextButton -// import androidx.compose.material3.TextField -// import androidx.compose.runtime.Composable -// import androidx.compose.runtime.getValue -// import androidx.compose.runtime.mutableStateOf -// import androidx.compose.runtime.remember -// import androidx.compose.runtime.setValue -// import androidx.compose.ui.Modifier -// import androidx.compose.ui.platform.LocalContext -// import androidx.compose.ui.tooling.preview.Preview -// import androidx.compose.ui.unit.dp -// import kotlinx.datetime.Clock -// import kotlinx.datetime.DateTimeUnit -// import kotlinx.datetime.Instant -// import kotlinx.datetime.LocalDate -// import kotlinx.datetime.LocalDateTime -// import kotlinx.datetime.LocalTime -// import kotlinx.datetime.TimeZone -// import kotlinx.datetime.plus -// import kotlinx.datetime.toInstant -// import kotlinx.datetime.toLocalDateTime -// import kotlinx.datetime.todayIn -// import kotlin.time.DurationUnit -// -// @Composable -// fun NotificationDialog( -// showDialog: Boolean = true, -// onDismissRequest: () -> Unit = {}, -// remainder: Long = -1, -// interval: Long? = null, -// onSetAlarm: (Long, Long?) -> Unit = { _, _ -> }, -// onDeleteAlarm: () -> Unit = {}, -// ) { -// val now = remember { -// Clock.System.now() -// } -// var showDate by remember { -// mutableStateOf(false) -// } -// var showTime by remember { -// mutableStateOf(false) -// } -// var dateTime by remember(remainder) { -// val time = if (remainder > 0) { -// Instant.fromEpochMilliseconds(remainder) -// .toLocalDateTime(TimeZone.currentSystemDefault()) -// } else { -// now.toLocalDateTime(TimeZone.currentSystemDefault()) -// } -// -// mutableStateOf(time) -// } -// var inter by remember(interval) { -// mutableStateOf(interval) -// } -// val context = LocalContext.current -// -// AnimatedVisibility(visible = showDialog) { -// AlertDialog( -// onDismissRequest = onDismissRequest, -// title = { Text(text = if (remainder > 0) "Edit Reminder" else "Add Reminder") }, -// text = { -// TimeContent( -// inter, -// dateTime, -// onDateChange = { -// if (it.time == LocalTime(0, 0)) { -// -// showTime = true -// } else { -// if (it.date == LocalDate(1993, 1, 1)) { -// showDate = true -// } else { -// dateTime = it -// } -// } -// }, -// onIntervalChange = { -// inter = it -// }, -// ) -// }, -// confirmButton = { -// Button(onClick = { -// val va = dateTime.toInstant(TimeZone.UTC).toEpochMilliseconds() -// if (va > now.toEpochMilliseconds()) { -// onDismissRequest() -// onSetAlarm( -// dateTime.toInstant(TimeZone.currentSystemDefault()) -// .toEpochMilliseconds(), -// inter, -// ) -// } -// }) { -// Text(text = "Save") -// } -// }, -// dismissButton = { -// Row { -// if (remainder > 0) { -// TextButton(onClick = { -// onDismissRequest() -// onDeleteAlarm() -// }) { -// Text(text = "Delete") -// } -// Spacer(modifier = Modifier.width(8.dp)) -// } -// TextButton(onClick = { onDismissRequest() }) { -// Text(text = "Cancel") -// } -// } -// }, -// ) -// } -// -// // TimeDialog( -// // showDialog=showTime, -// // hour = dateTime.hour, -// // minute = dateTime.minute, -// // onDismissRequest = {showTime=false}, -// // onSetTime = {dateTime= LocalDateTime(dateTime.date,it) } -// // ) -// // DateDialog( -// // showDialog=showDate, -// // currentDate = dateTime.toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds(), -// // onDismissRequest = {showDate=false}, -// // onSetDate = { -// // -// // dateTime=LocalDateTime(it,dateTime.time)} -// // ) -// } -// -// @Preview -// @Composable -// fun NotificationDialogPreview() { -// NotificationDialog( -// remainder = LocalDateTime(2023, 2, 14, 12, 30).toInstant(TimeZone.currentSystemDefault()) -// .toEpochMilliseconds() -// ) -// } -// -// @Composable -// fun TimeContent( -// interval: Long? = null, -// dateTime: LocalDateTime, -// onDateChange: (LocalDateTime) -> Unit = {}, -// onIntervalChange: (Long?) -> Unit = {}, -// ) { -// val instant = dateTime.toInstant(TimeZone.UTC) -// -// Column { -// TimeDropbox( -// value = instant.toEpochMilliseconds(), -// onValueChange = { -// onDateChange(LocalDateTime(dateTime.date, it)) -// }, -// ) -// DateDropbox( -// value = instant.toEpochMilliseconds(), -// onValueChange = { -// onDateChange(LocalDateTime(it, dateTime.time)) -// }, -// ) -// RepeatDropbox( -// value = interval, -// onValueChange = onIntervalChange, -// ) -// } -// } -// -// @OptIn(ExperimentalMaterial3Api::class) -// @Composable -// fun TimeDropbox(value: Long, onValueChange: (LocalTime) -> Unit = {}) { -// var expanded by remember { -// mutableStateOf(false) -// } -// -// val options = -// remember { -// listOf( -// Pair("Morning", LocalTime(7, 0)), -// Pair("Afternoon", LocalTime(13, 0)), -// Pair("Evening", LocalTime(19, 0)), -// Pair("Night", LocalTime(20, 0)), -// Pair("Pick time", LocalTime(0, 0)), -// ) -// } -// val lastIndex = remember { -// options.lastIndex -// } -// val now = remember { -// Clock.System.now() -// } -// val nowtime = remember { -// now.toLocalDateTime(TimeZone.UTC).time -// } -// -// ExposedDropdownMenuBox( -// modifier = Modifier, -// expanded = expanded, -// onExpandedChange = { expanded = !expanded }, -// ) { -// TextField( -// modifier = Modifier.menuAnchor(), -// readOnly = true, -// value = value.toTimeString(), -// supportingText = { if (now.toEpochMilliseconds() > value) Text(text = "Time as past") }, -// isError = now.toEpochMilliseconds() > value, -// onValueChange = {}, -// trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, -// colors = ExposedDropdownMenuDefaults.textFieldColors(), -// singleLine = true, -// -// ) -// ExposedDropdownMenu(expanded = expanded, onDismissRequest = { -// expanded = false -// }) { -// options.forEachIndexed { index, pair -> -// DropdownMenuItem( -// text = { Text(text = pair.first) }, -// onClick = { -// onValueChange(pair.second) -// expanded = false -// }, -// enabled = if (index != lastIndex) pair.second > nowtime else true, -// trailingIcon = { -// if (index != lastIndex) { -// Text( -// text = pair.second.toMillisecondOfDay().toLong().toTimeString(), -// ) -// } -// }, -// ) -// } -// } -// } -// } -// -// @OptIn(ExperimentalMaterial3Api::class) -// @Composable -// fun DateDropbox(value: Long, onValueChange: (LocalDate) -> Unit = {}) { -// var expanded by remember { -// mutableStateOf(false) -// } -// -// val date = remember(value) { -// Clock.System.todayIn(TimeZone.UTC) -// } -// val options = remember { -// listOf( -// Pair("Today", date), -// Pair("Tomorrow", date.plus(DateTimeUnit.DAY)), -// Pair("Pick date", LocalDate(1993, 1, 1)), -// ) -// } -// ExposedDropdownMenuBox( -// modifier = Modifier, -// expanded = expanded, -// onExpandedChange = { expanded = !expanded }, -// ) { -// TextField( -// modifier = Modifier.menuAnchor(), -// readOnly = true, -// value = value.toDateString(), -// onValueChange = {}, -// trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, -// colors = ExposedDropdownMenuDefaults.textFieldColors(), -// singleLine = true, -// -// ) -// ExposedDropdownMenu(expanded = expanded, onDismissRequest = { -// expanded = false -// }) { -// options.forEach { string -> -// DropdownMenuItem( -// text = { Text(text = string.first) }, -// onClick = { -// onValueChange(string.second) -// expanded = false -// }, -// ) -// } -// } -// } -// } -// -// @OptIn(ExperimentalMaterial3Api::class) -// @Composable -// fun RepeatDropbox(value: Long?, onValueChange: (Long?) -> Unit = {}) { -// var expanded by remember { -// mutableStateOf(false) -// } -// -// val options = remember { -// listOf( -// Pair("Does not repeat", null), -// Pair("Daily", DateTimeUnit.HOUR.times(24).duration.toLong(DurationUnit.MILLISECONDS)), -// Pair( -// "Weekly", -// DateTimeUnit.HOUR.times(24 * 7).duration.toLong(DurationUnit.MILLISECONDS), -// ), -// Pair( -// "Monthly", -// DateTimeUnit.HOUR.times(24 * 7 * 30).duration.toLong(DurationUnit.MILLISECONDS), -// ), -// Pair( -// "Yearly", -// DateTimeUnit.HOUR.times(24 * 7 * 30).duration.toLong(DurationUnit.MILLISECONDS), -// ), -// // Pair("Pick time", 0L) -// ) -// } -// -// val index = remember(value) { -// val i = options.indexOfFirst { it.second == value } -// if (value == null || i == -1) { -// 0 -// } else { -// i -// } -// } -// -// ExposedDropdownMenuBox( -// modifier = Modifier, -// expanded = expanded, -// onExpandedChange = { expanded = !expanded }, -// ) { -// TextField( -// modifier = Modifier.menuAnchor(), -// readOnly = true, -// value = options[index].first, -// onValueChange = {}, -// trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, -// colors = ExposedDropdownMenuDefaults.textFieldColors(), -// singleLine = true, -// -// ) -// ExposedDropdownMenu(expanded = expanded, onDismissRequest = { -// expanded = false -// }) { -// options.forEach { string -> -// DropdownMenuItem( -// text = { Text(text = string.first) }, -// onClick = { -// onValueChange(string.second) -// expanded = false -// }, -// ) -// } -// } -// } -// } -// -// @Preview -// @Composable -// fun TimeColumnPreview() { -// TimeContent( -// dateTime = LocalDateTime(2021, 4, 5, 0, 0, 0), -// ) -// } -// diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/NotifySneackerUi.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/NotifySneackerUi.kt deleted file mode 100644 index e72ccd0b..00000000 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/NotifySneackerUi.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.mshdabiola.ui - -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import com.mshdabiola.ui.state.Notify -import kotlinx.collections.immutable.ImmutableList - -@Composable -fun NotifySnacker(snackHostState: SnackbarHostState, notifys: ImmutableList) { - LaunchedEffect(key1 = notifys, block = { - if (notifys.isNotEmpty()) { - val first = notifys.first() - val result = snackHostState.showSnackbar( - message = first.message, - withDismissAction = first.withDismissAction, - actionLabel = first.label, - duration = if (first.isShort) SnackbarDuration.Short else SnackbarDuration.Long, - ) - when (result) { - SnackbarResult.ActionPerformed -> { - } - - SnackbarResult.Dismissed -> { - first.callback() - } - } - } - }) -} diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeZoneBroadcastReceiver.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeZoneBroadcastReceiver.kt deleted file mode 100644 index 89ba7969..00000000 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/TimeZoneBroadcastReceiver.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - *abiola 2024 - */ - -package com.mshdabiola.ui - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter - -class TimeZoneBroadcastReceiver( - val onTimeZoneChanged: () -> Unit, -) : BroadcastReceiver() { - private var registered = false - - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_TIMEZONE_CHANGED) { - onTimeZoneChanged() - } - } - - fun register(context: Context) { - if (!registered) { - val filter = IntentFilter() - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED) - context.registerReceiver(this, filter) - registered = true - } - } - - fun unregister(context: Context) { - if (registered) { - context.unregisterReceiver(this) - registered = false - } - } -} From 459546d70879178abd46f2b116167da193ba7e72 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:00:45 +0100 Subject: [PATCH 008/790] Refactor: Replace TextButton with NoteTextButton in DetailScreen This commit replaces the `TextButton` composable with the custom `NoteTextButton` composable in `DetailScreen.kt`. - The "Add list item" button now uses `NoteTextButton`. - The button to show/hide checked items now uses `NoteTextButton` and its `leadingIcon` parameter is utilized for the expand/collapse icon. --- .../com/mshdabiola/detail/DetailScreen.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt index 38c8d559..29e4b3dc 100644 --- a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt +++ b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt @@ -50,7 +50,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -86,6 +85,7 @@ import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.component.NoteTextField import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.model.NoteCheck @@ -580,7 +580,7 @@ fun EditScreen( } item { - TextButton(onClick = addItem) { + NoteTextButton(onClick = addItem) { Icon(imageVector = NoteIcon.Add, contentDescription = "") Text(text = stringResource(Rd.string.modules_designsystem_add_list_item)) @@ -589,11 +589,15 @@ fun EditScreen( if (checkNote.isNotEmpty()) { item { - TextButton(onClick = { showCheckNote = !showCheckNote }) { - Icon( - imageVector = if (showCheckNote)NoteIcon.More else NoteIcon.Less, - contentDescription = "", - ) + NoteTextButton( + onClick = { showCheckNote = !showCheckNote }, + leadingIcon = { + Icon( + imageVector = if (showCheckNote)NoteIcon.More else NoteIcon.Less, + contentDescription = "", + ) + }, + ) { Text( text = "${checkNote.size} ${stringResource(Rd.string.modules_designsystem_checked_items)}", style = MaterialTheme.typography.titleMedium, From b34f5418e05520e9135f491238c04491d0c162cc Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:00:58 +0100 Subject: [PATCH 009/790] Refactor: Use Design System Components in DrawingBar This commit updates `DrawingBar.kt` to use UI components from the design system module. - Replaced `TabRow` with `NoteTabRow`. - Replaced `Tab` with `NoteTab`. - Replaced `TextButton` with `NoteTextButton`. - Removed `unselectedContentColor` from tabs as this is handled by the `NoteTab` component. --- .../java/com/mshdabiola/drawing/DrawingBar.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingBar.kt b/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingBar.kt index ced6220e..a7f523e4 100644 --- a/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingBar.kt +++ b/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingBar.kt @@ -21,10 +21,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -40,6 +37,9 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.component.NoteTab +import com.mshdabiola.designsystem.component.NoteTabRow +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.model.DRAW_MODE import com.mshdabiola.ui.FlowLayout2 import kotlinx.coroutines.launch @@ -87,10 +87,10 @@ fun DrawingBar( val coroutineScope = rememberCoroutineScope() Surface(modifier) { Column { - TabRow( + NoteTabRow( selectedTabIndex = pagerState.currentPage, ) { - Tab( + NoteTab( selected = pagerState.currentPage == 0, onClick = { controller.draw_mode = DRAW_MODE.ERASE @@ -116,7 +116,7 @@ fun DrawingBar( ) } } - Tab( + NoteTab( selected = pagerState.currentPage == 1, onClick = { controller.draw_mode = DRAW_MODE.PEN @@ -146,9 +146,8 @@ fun DrawingBar( } } - Tab( + NoteTab( selected = pagerState.currentPage == 2, - unselectedContentColor = Color.Gray, onClick = { controller.draw_mode = DRAW_MODE.MARKER controller.colorAlpha = 1f @@ -176,9 +175,8 @@ fun DrawingBar( ) } } - Tab( + NoteTab( selected = pagerState.currentPage == 3, - unselectedContentColor = Color.Gray, onClick = { controller.draw_mode = DRAW_MODE.CRAYON controller.colorAlpha = 0.5f @@ -227,7 +225,7 @@ fun DrawingBar( // 0 -> { - TextButton(onClick = { controller.clearPath() }) { + NoteTextButton(onClick = { controller.clearPath() }) { Text(text = stringResource(Rd.string.modules_designsystem_clear_canvas)) } } From 92a431047fafb430f862dda6c9181705f96e65cc Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:01:08 +0100 Subject: [PATCH 010/790] Refactor: Use Design System Components for Buttons This commit replaces `Button` and `TextButton` with their design system equivalents (`NoteButton` and `NoteTextButton`) in `MainScreen.kt` and `TopbarAndDialog.kt`. - Replaced `Button` with `NoteButton` in `NameNoteAlertDialog`. - Replaced `TextButton` with `NoteTextButton` in `NameNoteAlertDialog`, `DeleteNoteAlertDialog`, and `BottomNoteItem`. - In `TopbarAndDialog.kt`, the `onClick` and `onLongClick` lambdas in `MainCard` were simplified by directly passing `notePad.id` to the respective functions. --- .../kotlin/com/mshdabiola/main/MainScreen.kt | 14 +++++++------- .../com/mshdabiola/main/TopbarAndDialog.kt | 16 ++++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt index c6b98ae9..e9f52960 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt @@ -39,14 +39,12 @@ import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -76,7 +74,9 @@ import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.rememberLottieComposition import com.mshdabiola.analytics.LocalAnalyticsHelper import com.mshdabiola.common.result.Result +import com.mshdabiola.designsystem.component.NoteButton import com.mshdabiola.designsystem.component.NoteLoadingWheel +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.model.Note import com.mshdabiola.model.NotePad @@ -627,7 +627,7 @@ fun RenameLabelAlertDialog( TextField(value = name, onValueChange = { name = it }) }, confirmButton = { - Button( + NoteButton( onClick = { onDismissRequest() onChangeName(name) @@ -637,7 +637,7 @@ fun RenameLabelAlertDialog( } }, dismissButton = { - TextButton(onClick = { onDismissRequest() }) { + NoteTextButton(onClick = { onDismissRequest() }) { Text(text = stringResource(Rd.string.modules_designsystem_cancel)) } }, @@ -665,7 +665,7 @@ fun DeleteLabelAlertDialog( Text(text = stringResource(Rd.string.modules_designsystem_rename_label_detail)) }, confirmButton = { - TextButton( + NoteTextButton( onClick = { onDismissRequest() onDelete() @@ -675,7 +675,7 @@ fun DeleteLabelAlertDialog( } }, dismissButton = { - TextButton(onClick = { onDismissRequest() }) { + NoteTextButton(onClick = { onDismissRequest() }) { Text(text = stringResource(Rd.string.modules_designsystem_cancel)) } }, @@ -721,7 +721,7 @@ fun LabelBox( ) { Text(modifier = Modifier.weight(1f), text = title) if (list.size > 3) { - TextButton(onClick = { showMore = !showMore }) { + NoteTextButton(onClick = { showMore = !showMore }) { Text( text = if (!showMore) { stringResource(id = Rd.string.modules_designsystem_more) diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt index f4f02553..6edb28ad 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material3.AlertDialog import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.Button import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -32,7 +31,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults @@ -61,6 +59,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import com.mshdabiola.designsystem.component.NoteButton +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.component.NoteTextField import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.model.NoteCheck @@ -469,7 +469,7 @@ fun RenameLabelAlertDialog( TextField(value = name, onValueChange = { name = it }) }, confirmButton = { - Button( + NoteButton( onClick = { onDismissRequest() onChangeName(name) @@ -479,7 +479,7 @@ fun RenameLabelAlertDialog( } }, dismissButton = { - TextButton(onClick = { onDismissRequest() }) { + NoteTextButton(onClick = { onDismissRequest() }) { Text(text = stringResource(Rd.string.modules_designsystem_cancel)) } }, @@ -507,7 +507,7 @@ fun DeleteLabelAlertDialog( Text(text = stringResource(Rd.string.modules_designsystem_rename_details)) }, confirmButton = { - TextButton( + NoteTextButton( onClick = { onDismissRequest() onDelete() @@ -517,7 +517,7 @@ fun DeleteLabelAlertDialog( } }, dismissButton = { - TextButton(onClick = { onDismissRequest() }) { + NoteTextButton(onClick = { onDismissRequest() }) { Text(text = stringResource(Rd.string.modules_designsystem_cancel)) } }, @@ -635,8 +635,8 @@ fun NoteCard( OutlinedCard( modifier = modifier.combinedClickable( - onClick = { notePad.id.let { onCardClick(it) } }, - onLongClick = { notePad.id.let { onLongClick(it) } }, + onClick = { onCardClick(notePad.id) }, + onLongClick = { onLongClick(notePad.id) }, ), border = if (notePad.selected) { BorderStroke(3.dp, Color.Blue) From b02b075d320076f86d1a11a37ac298899f1e069e Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:01:19 +0100 Subject: [PATCH 011/790] Refactor: Use NoteTextButton in LabelScreen This commit replaces the `TextButton` with the custom `NoteTextButton` composable in `LabelScreen.kt`. - In `LabelScreen.kt`, when `labelScreenUiState.showAddLabel` is true, the UI now uses `NoteTextButton` for the "create label" action. This change utilizes the leadingIcon parameter of `NoteTextButton` to display an add icon. --- .../com/mshdabiola/selectlabelscreen/LabelScreen.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/feature/selectlabelscreen/src/main/java/com/mshdabiola/selectlabelscreen/LabelScreen.kt b/feature/selectlabelscreen/src/main/java/com/mshdabiola/selectlabelscreen/LabelScreen.kt index 0a10f850..0fefbcf8 100644 --- a/feature/selectlabelscreen/src/main/java/com/mshdabiola/selectlabelscreen/LabelScreen.kt +++ b/feature/selectlabelscreen/src/main/java/com/mshdabiola/selectlabelscreen/LabelScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TriStateCheckbox import androidx.compose.runtime.Composable @@ -24,6 +23,7 @@ import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.component.NoteTextField import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.ui.FirebaseScreenLog @@ -72,9 +72,12 @@ fun LabelScreen( ) { paddingValues -> Column(Modifier.padding(paddingValues)) { if (labelScreenUiState.showAddLabel) { - TextButton(onClick = { onCreateLabel() }) { - Icon(imageVector = NoteIcon.Add, contentDescription = "add") - Spacer(modifier = Modifier.width(16.dp)) + NoteTextButton( + onClick = { onCreateLabel() }, + leadingIcon = { + Icon(imageVector = NoteIcon.Add, contentDescription = "add") + }, + ) { Text(text = "${stringResource(id = Rd.string.modules_designsystem_create)} \"${labelScreenUiState.editText}\"") } } From d9a9a8da624b3ba3c4c4acb80d7525bf06a9aff7 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:01:29 +0100 Subject: [PATCH 012/790] Refactor: Update OptionsDialog to use NoteTextButton This commit updates the `OptionsDialog` composable in `OptionsDialog.kt` to use the `NoteTextButton` component from the design system module for its confirm button. - Replaced `TextButton` with `NoteTextButton` for the dialog's confirm button. --- .../src/main/kotlin/com/mshdabiola/setting/OptionsDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/setting/src/main/kotlin/com/mshdabiola/setting/OptionsDialog.kt b/feature/setting/src/main/kotlin/com/mshdabiola/setting/OptionsDialog.kt index 087212ac..819fa5f2 100644 --- a/feature/setting/src/main/kotlin/com/mshdabiola/setting/OptionsDialog.kt +++ b/feature/setting/src/main/kotlin/com/mshdabiola/setting/OptionsDialog.kt @@ -7,13 +7,13 @@ import androidx.compose.foundation.layout.Row import androidx.compose.material3.AlertDialog import androidx.compose.material3.RadioButton import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.R as Rd @Composable @@ -28,7 +28,7 @@ fun OptionsDialog( modifier = modifier, onDismissRequest = onDismiss, confirmButton = { - TextButton(onClick = onDismiss) { + NoteTextButton(onClick = onDismiss) { Text(stringResource(Rd.string.modules_designsystem_close)) } }, From 1d788b5721aac1c65480df8d3da442778d252b2d Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:01:47 +0100 Subject: [PATCH 013/790] Chore: Add kotlin-test dependency This commit adds the `kotlin-test` artifact to the project's dependencies. - In `gradle/libs.versions.toml`, `kotlin-test` is added to the `[libraries]` section. --- gradle/libs.versions.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 869b07ff..b17336d2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -166,6 +166,7 @@ hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.r hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltExt" } javax-inject = { module = "javax.inject:javax.inject", version = "1" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" } kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } From b23cf93db741b27e0dfd2ee1d728d6250355f646 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:01:58 +0100 Subject: [PATCH 014/790] Refactor: Replace `TextButton` with `NoteTextButton` and `SkTheme` with `NoteTheme` This commit replaces the Material `TextButton` with the custom `NoteTextButton` and the `SkTheme` with `NoteTheme` across several files. - In `MainNavigatin.kt` and `ShareActivity.kt`, instances of `TextButton` have been updated to `NoteTextButton`. - In `MainActivity.kt` and `ShareActivity.kt`, `SkTheme` has been replaced with `NoteTheme`. --- .../kotlin/com/mshdabiola/playnotepad/MainActivity.kt | 4 ++-- .../java/com/mshdabiola/playnotepad/ShareActivity.kt | 10 +++++----- .../com/mshdabiola/playnotepad/ui/MainNavigatin.kt | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/fossReliant/kotlin/com/mshdabiola/playnotepad/MainActivity.kt b/app/src/fossReliant/kotlin/com/mshdabiola/playnotepad/MainActivity.kt index ace69d85..d16b259c 100644 --- a/app/src/fossReliant/kotlin/com/mshdabiola/playnotepad/MainActivity.kt +++ b/app/src/fossReliant/kotlin/com/mshdabiola/playnotepad/MainActivity.kt @@ -26,7 +26,7 @@ import androidx.metrics.performance.JankStats import com.mshdabiola.analytics.AnalyticsHelper import com.mshdabiola.analytics.LocalAnalyticsHelper import com.mshdabiola.data.util.NetworkMonitor -import com.mshdabiola.designsystem.theme.SkTheme +import com.mshdabiola.designsystem.theme.NoteTheme import com.mshdabiola.model.DarkThemeConfig import com.mshdabiola.model.ThemeBrand import com.mshdabiola.playnotepad.ui.NoteApp @@ -97,7 +97,7 @@ class MainActivity : ComponentActivity() { ) CompositionLocalProvider(LocalAnalyticsHelper provides analyticsHelper) { - SkTheme( + NoteTheme( androidTheme = shouldUseAndroidTheme(uiState), darkTheme = darkTheme, disableDynamicTheming = shouldDisableDynamicTheming(uiState), diff --git a/app/src/main/java/com/mshdabiola/playnotepad/ShareActivity.kt b/app/src/main/java/com/mshdabiola/playnotepad/ShareActivity.kt index eb57bf3c..e03b71e1 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/ShareActivity.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/ShareActivity.kt @@ -31,7 +31,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -50,9 +49,10 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import coil3.compose.AsyncImage +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.component.NoteTextField import com.mshdabiola.designsystem.icon.NoteIcon -import com.mshdabiola.designsystem.theme.SkTheme +import com.mshdabiola.designsystem.theme.NoteTheme import com.mshdabiola.model.DarkThemeConfig import com.mshdabiola.model.Label import com.mshdabiola.model.ThemeBrand @@ -122,7 +122,7 @@ class ShareActivity : ComponentActivity() { onDispose {} } - SkTheme( + NoteTheme( darkTheme = darkTheme, disableDynamicTheming = shouldDisableDynamicTheming(uiState), ) { @@ -314,7 +314,7 @@ fun ActionEditScreen( } if (showLabel) { item { - TextButton(onClick = { showLabelDialog() }) { + NoteTextButton(onClick = { showLabelDialog() }) { Text(stringResource(Rd.string.modules_designsystem_add_labels)) } } @@ -338,7 +338,7 @@ fun EditLabels( Text(stringResource(Rd.string.modules_designsystem_add_labels)) }, confirmButton = { - TextButton(onClick = { onDismissRequest() }) { + NoteTextButton(onClick = { onDismissRequest() }) { Text(stringResource(Rd.string.modules_designsystem_close)) } }, diff --git a/app/src/main/java/com/mshdabiola/playnotepad/ui/MainNavigatin.kt b/app/src/main/java/com/mshdabiola/playnotepad/ui/MainNavigatin.kt index e2828786..1ff246f0 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/ui/MainNavigatin.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/ui/MainNavigatin.kt @@ -18,7 +18,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -31,6 +30,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.model.Label import com.mshdabiola.model.NoteType @@ -113,7 +113,7 @@ fun MainNavigation( modifier = Modifier.weight(1f), text = stringResource(Rd.string.modules_designsystem_labels), ) - TextButton(onClick = { navigateToLevel(false) }) { + NoteTextButton(onClick = { navigateToLevel(false) }) { Text(text = stringResource(Rd.string.modules_designsystem_edit)) } } From 7fe13b9cfeff2d63b88e74183908f611a7a7ac05 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:11:50 +0100 Subject: [PATCH 015/790] Refactor: Remove unused design system components and simplify `LoadingWheel` This commit removes several unused composable functions from the design system module: - `DynamicAsyncImage` - `NoteIconToggleButton` and `NoteIconButtonDefaults` - `NoteTopicTag` and `SkTagDefaults` Additionally, the `NoteLoadingWheel` composable has been simplified by removing commented-out code. The `NoteOverlayLoadingWheel` composable, which was using `NoteLoadingWheel`, has also been removed as it's no longer used. --- .../component/DynamicAsyncImage.kt | 66 ------------- .../designsystem/component/IconButton.kt | 63 ------------- .../designsystem/component/LoadingWheel.kt | 92 ------------------- .../mshdabiola/designsystem/component/Tag.kt | 59 ------------ 4 files changed, 280 deletions(-) delete mode 100644 modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/DynamicAsyncImage.kt delete mode 100644 modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/IconButton.kt delete mode 100644 modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Tag.kt diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/DynamicAsyncImage.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/DynamicAsyncImage.kt deleted file mode 100644 index 824a32c0..00000000 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/DynamicAsyncImage.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - *abiola 2024 - */ - -package com.mshdabiola.designsystem.component - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import com.mshdabiola.designsystem.R -import com.mshdabiola.designsystem.theme.LocalTintTheme - -/** - * A wrapper around [AsyncImage] which determines the colorFilter based on the theme - */ -@Composable -fun DynamicAsyncImage( - imageUrl: String, - contentDescription: String?, - modifier: Modifier = Modifier, - placeholder: Painter = painterResource(R.drawable.modules_designsystem_ic_placeholder_default), -) { - val iconTint = LocalTintTheme.current.iconTint - var isLoading by remember { mutableStateOf(true) } - var isError by remember { mutableStateOf(false) } -// val imageLoader = rememberAsyncImagePainter( -// model = imageUrl, -// onState = { state -> -// isLoading = state is AsyncImagePainter.State.Loading -// isError = state is Error -// }, -// ) - val isLocalInspection = LocalInspectionMode.current - Box( - modifier = modifier, - contentAlignment = Alignment.Center, - ) { - if (isLoading && !isLocalInspection) { - // Display a progress bar while loading - CircularProgressIndicator( - modifier = Modifier - .align(Alignment.Center) - .size(80.dp), - color = MaterialTheme.colorScheme.tertiary, - ) - } -// Image( -// contentScale = ContentScale.Crop, -// painter = if (isError.not() && !isLocalInspection) imageLoader else placeholder, -// contentDescription = contentDescription, -// colorFilter = if (iconTint != Unspecified) ColorFilter.tint(iconTint) else null, -// ) - } -} diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/IconButton.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/IconButton.kt deleted file mode 100644 index 45f7b303..00000000 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/IconButton.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - *abiola 2022 - */ - -package com.mshdabiola.designsystem.component - -import androidx.compose.material3.FilledIconToggleButton -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color - -/** - * Notepad toggle button with icon and checked icon content slots. Wraps Material 3 - * [IconButton]. - * - * @param checked Whether the toggle button is currently checked. - * @param onCheckedChange Called when the user clicks the toggle button and toggles checked. - * @param modifier Modifier to be applied to the toggle button. - * @param enabled Controls the enabled state of the toggle button. When `false`, this toggle button - * will not be clickable and will appear disabled to accessibility services. - * @param icon The icon content to show when unchecked. - * @param checkedIcon The icon content to show when checked. - */ -@Composable -fun NoteIconToggleButton( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - icon: @Composable () -> Unit, - checkedIcon: @Composable () -> Unit = icon, -) { - // TODO: File bug - // Can't use regular IconToggleButton as it doesn't include a shape (appears square) - FilledIconToggleButton( - checked = checked, - onCheckedChange = onCheckedChange, - modifier = modifier, - enabled = enabled, - colors = IconButtonDefaults.iconToggleButtonColors( - checkedContainerColor = MaterialTheme.colorScheme.primaryContainer, - checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer, - disabledContainerColor = if (checked) { - MaterialTheme.colorScheme.onBackground.copy( - alpha = NoteIconButtonDefaults.DISABLED_ICON_BUTTON_CONTAINER_ALPHA, - ) - } else { - Color.Transparent - }, - ), - ) { - if (checked) checkedIcon() else icon() - } -} - -object NoteIconButtonDefaults { - // TODO: File bug - // IconToggleButton disabled container alpha not exposed by IconButtonDefaults - const val DISABLED_ICON_BUTTON_CONTAINER_ALPHA = 0.12f -} diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/LoadingWheel.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/LoadingWheel.kt index 9bbec3f6..172198ae 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/LoadingWheel.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/LoadingWheel.kt @@ -17,98 +17,6 @@ fun NoteLoadingWheel( modifier: Modifier = Modifier, ) { LoadingIndicator(modifier = modifier) -// val infiniteTransition = rememberInfiniteTransition(label = "wheel transition") -// -// // Specifies the float animation for slowly drawing out the lines on entering -// val startValue = if (LocalInspectionMode.current) 0F else 1F -// val floatAnimValues = (0 until NUM_OF_LINES).map { remember { Animatable(startValue) } } -// LaunchedEffect(floatAnimValues) { -// (0 until NUM_OF_LINES).map { index -> -// launch { -// floatAnimValues[index].animateTo( -// targetValue = 0F, -// animationSpec = tween( -// durationMillis = 100, -// easing = FastOutSlowInEasing, -// delayMillis = 40 * index, -// ), -// ) -// } -// } -// } -// -// // Specifies the rotation animation of the entire Canvas composable -// val rotationAnim by infiniteTransition.animateFloat( -// initialValue = 0F, -// targetValue = 360F, -// animationSpec = infiniteRepeatable( -// animation = tween(durationMillis = ROTATION_TIME, easing = LinearEasing), -// ), -// label = "wheel rotation animation", -// ) -// -// // Specifies the color animation for the base-to-progress line color change -// val baseLineColor = MaterialTheme.colorScheme.onBackground -// val progressLineColor = MaterialTheme.colorScheme.inversePrimary -// -// val colorAnimValues = (0 until NUM_OF_LINES).map { index -> -// infiniteTransition.animateColor( -// initialValue = baseLineColor, -// targetValue = baseLineColor, -// animationSpec = infiniteRepeatable( -// animation = keyframes { -// durationMillis = ROTATION_TIME / 2 -// progressLineColor at ROTATION_TIME / NUM_OF_LINES / 2 using LinearEasing -// baseLineColor at ROTATION_TIME / NUM_OF_LINES using LinearEasing -// }, -// repeatMode = RepeatMode.Restart, -// initialStartOffset = StartOffset(ROTATION_TIME / NUM_OF_LINES / 2 * index), -// ), -// label = "wheel color animation", -// ) -// } -// -// // Draws out the LoadingWheel Canvas composable and sets the animations -// Canvas( -// modifier = modifier -// .size(48.dp) -// .padding(8.dp) -// .graphicsLayer { rotationZ = rotationAnim } -// .semantics { contentDescription = contentDesc } -// .testTag("loadingWheel"), -// ) { -// repeat(NUM_OF_LINES) { index -> -// rotate(degrees = index * 30f) { -// drawLine( -// color = colorAnimValues[index].value, -// // Animates the initially drawn 1 pixel alpha from 0 to 1 -// alpha = if (floatAnimValues[index].value < 1f) 1f else 0f, -// strokeWidth = 4F, -// cap = StrokeCap.Round, -// start = Offset(size.width / 2, size.height / 4), -// end = Offset(size.width / 2, floatAnimValues[index].value * size.height / 4), -// ) -// } -// } -// } -// } -// -// @Composable -// fun NoteOverlayLoadingWheel( -// contentDesc: String, -// modifier: Modifier = Modifier, -// ) { -// Surface( -// shape = RoundedCornerShape(60.dp), -// shadowElevation = 8.dp, -// color = MaterialTheme.colorScheme.surface.copy(alpha = 0.83f), -// modifier = modifier -// .size(60.dp), -// ) { -// NoteLoadingWheel( -// contentDesc = contentDesc, -// ) -// } } private const val ROTATION_TIME = 12000 diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Tag.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Tag.kt deleted file mode 100644 index 3e262c78..00000000 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/Tag.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - *abiola 2022 - */ - -package com.mshdabiola.designsystem.component - -import androidx.compose.foundation.layout.Box -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ProvideTextStyle -import androidx.compose.material3.TextButton -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@Composable -fun NoteTopicTag( - modifier: Modifier = Modifier, - followed: Boolean, - onClick: () -> Unit, - enabled: Boolean = true, - text: @Composable () -> Unit, -) { - Box(modifier = modifier) { - val containerColor = if (followed) { - MaterialTheme.colorScheme.primaryContainer - } else { - MaterialTheme.colorScheme.surfaceVariant.copy( - alpha = SkTagDefaults.UNFOLLOWED_TOPIC_TAG_CONTAINER_ALPHA, - ) - } - TextButton( - onClick = onClick, - enabled = enabled, - colors = ButtonDefaults.textButtonColors( - containerColor = containerColor, - contentColor = contentColorFor(backgroundColor = containerColor), - disabledContainerColor = MaterialTheme.colorScheme.onSurface.copy( - alpha = SkTagDefaults.DISABLED_TOPIC_TAG_CONTAINER_ALPHA, - ), - ), - ) { - ProvideTextStyle(value = MaterialTheme.typography.labelSmall) { - text() - } - } - } -} - -/** - * Notepad tag default values. - */ -object SkTagDefaults { - const val UNFOLLOWED_TOPIC_TAG_CONTAINER_ALPHA = 0.5f - - // TODO: File bug - // Button disabled container alpha value not exposed by ButtonDefaults - const val DISABLED_TOPIC_TAG_CONTAINER_ALPHA = 0.12f -} From edd74a48075e51b44edbbf0849f8d46e02935083 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:43:48 +0100 Subject: [PATCH 016/790] Refactor: Simplify `NoteTopAppBar` and update usages This commit refactors the `NoteTopAppBar` composable for better flexibility and updates its usage across various screens. - Simplified `NoteTopAppBar` in `designsystem/component/TopAppBar.kt` by: - Removing `titleRes`, `navigationIcon`, `navigationIconContentDescription`, `actionIcon`, and `actionIconContentDescription` parameters. - Adding parameters for `title` (String), `subTitle` (String), `navigationIcon` (Composable), `alignment` (Alignment.Horizontal), `actions` (Composable RowScope), and `scrollBehavior` (TopAppBarScrollBehavior). - Removed the `NoteDetailTopAppBar` composable as its functionality is now covered by the refactored `NoteTopAppBar`. - Updated usages of `TopAppBar` to `NoteTopAppBar` in: - `DrawingScreen.kt` - `DetailScreen.kt` - `GalleryScreen.kt` - `AboutScreen.kt` - `LabeScreen.kt` - Modified `TopAppBarScreenShot.kt` to reflect the changes in `NoteTopAppBar` parameters. --- .../java/com/mshdabiola/about/AboutScreen.kt | 8 +- .../com/mshdabiola/detail/DetailScreen.kt | 4 +- .../com/mshdabiola/drawing/DrawingScreen.kt | 7 +- .../com/mshdabiola/gallery/GalleryScreen.kt | 5 +- .../com/mshdabiola/labelscreen/LabeScreen.kt | 7 +- .../designsystem/component/TopAppBar.kt | 82 ++++--------------- .../com/mshdabiola/designsystem/ScreenShot.kt | 6 +- 7 files changed, 32 insertions(+), 87 deletions(-) diff --git a/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt b/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt index 9dbf4c22..b4595ba7 100644 --- a/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt +++ b/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.mshdabiola.designsystem.component.NoteTopAppBar import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.ui.FirebaseScreenLog import kotlinx.datetime.Instant @@ -57,15 +58,14 @@ fun AboutScreen( Scaffold( topBar = { - TopAppBar( + NoteTopAppBar( navigationIcon = { IconButton(onClick = onBack) { Icon(imageVector = NoteIcon.ArrowBack, contentDescription = "back") } }, - title = { - Text(text = stringResource(Rd.string.modules_designsystem_about)) - }, + title = stringResource(Rd.string.modules_designsystem_about) + ) }, ) { paddingValues -> diff --git a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt index 29e4b3dc..67f3ae8b 100644 --- a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt +++ b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt @@ -87,6 +87,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.mshdabiola.designsystem.component.NoteTextButton import com.mshdabiola.designsystem.component.NoteTextField +import com.mshdabiola.designsystem.component.NoteTopAppBar import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.model.NoteCheck import com.mshdabiola.model.NotePad @@ -405,8 +406,7 @@ fun EditScreen( } }, topBar = { - TopAppBar( - title = { }, + NoteTopAppBar( colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), navigationIcon = { IconButton( diff --git a/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt b/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt index b525ff28..24f886dd 100644 --- a/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt +++ b/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt @@ -33,6 +33,7 @@ import androidx.core.content.FileProvider import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import com.mshdabiola.designsystem.component.NoteTopAppBar import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.ui.FirebaseScreenLog import java.io.File @@ -116,7 +117,7 @@ fun DrawingScreen( Scaffold( topBar = { - TopAppBar( + NoteTopAppBar( navigationIcon = { IconButton(onClick = onBackk) { Icon( @@ -125,9 +126,7 @@ fun DrawingScreen( ) } }, - title = { - Text(stringResource(Rd.string.modules_designsystem_drawing)) - }, + title = stringResource(Rd.string.modules_designsystem_drawing), actions = { IconButton( diff --git a/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt b/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt index 1b0c0326..ac05ebfe 100644 --- a/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt +++ b/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt @@ -34,6 +34,7 @@ import androidx.core.app.ShareCompat import androidx.core.content.FileProvider import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.mshdabiola.designsystem.component.NoteTopAppBar import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.ui.FirebaseScreenLog import kotlinx.coroutines.delay @@ -169,13 +170,13 @@ fun GalleryTopAppBar( mutableStateOf(false) } - TopAppBar( + NoteTopAppBar( navigationIcon = { IconButton(onClick = onBack) { Icon(imageVector = NoteIcon.ArrowBack, contentDescription = "back") } }, - title = { Text(text = name) }, + title = name, actions = { Box { IconButton(onClick = { showDropDown = true }) { diff --git a/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt b/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt index 3f6b00cd..8e12a858 100644 --- a/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt +++ b/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import com.mshdabiola.designsystem.component.NoteTextField +import com.mshdabiola.designsystem.component.NoteTopAppBar import com.mshdabiola.designsystem.icon.NoteIcon import com.mshdabiola.ui.FirebaseScreenLog import kotlinx.collections.immutable.toImmutableList @@ -58,15 +59,13 @@ fun LabelScreen( ) { Scaffold( topBar = { - TopAppBar( + NoteTopAppBar( navigationIcon = { IconButton(onClick = onBack) { Icon(imageVector = NoteIcon.ArrowBack, contentDescription = "back") } }, - title = { - Text(text = stringResource(Rd.string.modules_designsystem_edit_label)) - }, + title =stringResource(Rd.string.modules_designsystem_edit_label), ) }, ) { paddingValues -> diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt index 35e262a8..3a48de7e 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt @@ -6,6 +6,7 @@ package com.mshdabiola.designsystem.component +import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Delete @@ -19,85 +20,34 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun NoteTopAppBar( - titleRes: String, - navigationIcon: ImageVector, - navigationIconContentDescription: String, - actionIcon: ImageVector, - actionIconContentDescription: String, - modifier: Modifier = Modifier, - colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(), - onNavigationClick: () -> Unit = {}, - onActionClick: () -> Unit = {}, - -) { - CenterAlignedTopAppBar( - title = { Text(text = titleRes) }, - navigationIcon = { - IconButton(onClick = onNavigationClick) { - Icon( - imageVector = navigationIcon, - contentDescription = navigationIconContentDescription, - // tint = MaterialTheme.colorScheme.onSurface, - ) - } - }, - actions = { - IconButton(onClick = onActionClick) { - Icon( - imageVector = actionIcon, - contentDescription = actionIconContentDescription, - // tint = MaterialTheme.colorScheme.onSurface, - ) - } - }, - colors = colors, - modifier = modifier.testTag("skTopAppBar"), - ) -} @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable -fun NoteDetailTopAppBar( +fun NoteTopAppBar( modifier: Modifier = Modifier, - colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = MaterialTheme.colorScheme.background), - onNavigationClick: () -> Unit = {}, - onDeleteClick: () -> Unit = {}, + title : String="", + subTitle:String="", + navigationIcon: @Composable () -> Unit = {}, alignment: Alignment.Horizontal = Alignment.Start, -) { + actions: @Composable RowScope.() -> Unit = {}, + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), + scrollBehavior: TopAppBarScrollBehavior? = null, + ) { TopAppBar( - title = { Text(text = "") }, - navigationIcon = { - IconButton(onClick = onNavigationClick) { - Icon( - modifier = Modifier.testTag("back"), - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "back", - tint = MaterialTheme.colorScheme.onSurface, - ) - } - }, - subtitle = {}, + modifier = modifier.testTag("topAppBar"), + title = { Text(text = title) }, + navigationIcon = navigationIcon, titleHorizontalAlignment = alignment, - actions = { - IconButton(onClick = onDeleteClick) { - Icon( - modifier = Modifier.testTag("delete"), - imageVector = Icons.Default.Delete, - contentDescription = "delete", - tint = MaterialTheme.colorScheme.onSurface, - ) - } - }, + actions = actions, + subtitle = {}, colors = colors, - modifier = modifier.testTag("detailTopAppBar"), + scrollBehavior = scrollBehavior ) } diff --git a/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt b/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt index c48b20f4..c0101ea2 100644 --- a/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt +++ b/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt @@ -12,10 +12,6 @@ import com.mshdabiola.designsystem.icon.NoteIcon @Composable private fun TopAppBarScreenShot() { NoteTopAppBar( - titleRes = stringResource(android.R.string.untitled), - navigationIcon = NoteIcon.Search, - navigationIconContentDescription = "Navigation icon", - actionIcon = NoteIcon.MoreVert, - actionIconContentDescription = "Action icon", + title = stringResource(android.R.string.untitled), ) } From 45777fb5fd01542190e90db922097d1cdcf478f0 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Wed, 11 Jun 2025 08:52:00 +0100 Subject: [PATCH 017/790] Refactor: Consolidate TopAppBar usage and remove unused imports This commit refactors the TopAppBar implementation across multiple feature modules to use the centralized `NoteTopAppBar` from the design system module. It also removes unused `TopAppBar` imports from these modules. - In `feature/drawing/DrawingScreen.kt`, `feature/detail/DetailScreen.kt`, `feature/gallery/GalleryScreen.kt`, `feature/about/AboutScreen.kt`, and `feature/labelscreen/LabeScreen.kt`: - Replaced direct `androidx.compose.material3.TopAppBar` usage with `com.mshdabiola.designsystem.component.NoteTopAppBar`. - Removed the unused import for `androidx.compose.material3.TopAppBar`. - In `modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt`: - Removed unused imports like `Icons`, `ArrowBack`, `Delete`, `CenterAlignedTopAppBar`, `Icon`, `IconButton`, and `MaterialTheme`. - Minor code formatting adjustments. - In `modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt`: - Removed unused import for `NoteIcon`. --- .../java/com/mshdabiola/about/AboutScreen.kt | 3 +-- .../com/mshdabiola/detail/DetailScreen.kt | 1 - .../com/mshdabiola/drawing/DrawingScreen.kt | 1 - .../com/mshdabiola/gallery/GalleryScreen.kt | 3 +-- .../com/mshdabiola/labelscreen/LabeScreen.kt | 4 +--- .../designsystem/component/TopAppBar.kt | 17 ++++------------- .../com/mshdabiola/designsystem/ScreenShot.kt | 1 - 7 files changed, 7 insertions(+), 23 deletions(-) diff --git a/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt b/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt index b4595ba7..d3f3f69f 100644 --- a/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt +++ b/feature/about/src/main/java/com/mshdabiola/about/AboutScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -64,7 +63,7 @@ fun AboutScreen( Icon(imageVector = NoteIcon.ArrowBack, contentDescription = "back") } }, - title = stringResource(Rd.string.modules_designsystem_about) + title = stringResource(Rd.string.modules_designsystem_about), ) }, diff --git a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt index 67f3ae8b..6c1eae14 100644 --- a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt +++ b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt @@ -50,7 +50,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect diff --git a/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt b/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt index 24f886dd..02de10b9 100644 --- a/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt +++ b/feature/drawing/src/main/java/com/mshdabiola/drawing/DrawingScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue diff --git a/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt b/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt index ac05ebfe..91e07182 100644 --- a/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt +++ b/feature/gallery/src/main/java/com/mshdabiola/gallery/GalleryScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -176,7 +175,7 @@ fun GalleryTopAppBar( Icon(imageVector = NoteIcon.ArrowBack, contentDescription = "back") } }, - title = name, + title = name, actions = { Box { IconButton(onClick = { showDropDown = true }) { diff --git a/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt b/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt index 8e12a858..652d771a 100644 --- a/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt +++ b/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabeScreen.kt @@ -8,8 +8,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -65,7 +63,7 @@ fun LabelScreen( Icon(imageVector = NoteIcon.ArrowBack, contentDescription = "back") } }, - title =stringResource(Rd.string.modules_designsystem_edit_label), + title = stringResource(Rd.string.modules_designsystem_edit_label), ) }, ) { paddingValues -> diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt index 3a48de7e..9015a8f3 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/component/TopAppBar.kt @@ -7,15 +7,8 @@ package com.mshdabiola.designsystem.component import androidx.compose.foundation.layout.RowScope -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarColors @@ -24,22 +17,20 @@ import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag - @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun NoteTopAppBar( modifier: Modifier = Modifier, - title : String="", - subTitle:String="", + title: String = "", + subTitle: String = "", navigationIcon: @Composable () -> Unit = {}, alignment: Alignment.Horizontal = Alignment.Start, actions: @Composable RowScope.() -> Unit = {}, colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null, - ) { +) { TopAppBar( modifier = modifier.testTag("topAppBar"), title = { Text(text = title) }, @@ -48,6 +39,6 @@ fun NoteTopAppBar( actions = actions, subtitle = {}, colors = colors, - scrollBehavior = scrollBehavior + scrollBehavior = scrollBehavior, ) } diff --git a/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt b/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt index c0101ea2..81846a5b 100644 --- a/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt +++ b/modules/designsystem/src/screenshotTest/kotlin/com/mshdabiola/designsystem/ScreenShot.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.mshdabiola.designsystem.component.NoteTopAppBar -import com.mshdabiola.designsystem.icon.NoteIcon @OptIn(ExperimentalMaterial3Api::class) @Preview From b4636e3098157cef649d090c9e8bae467777465d Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Thu, 12 Jun 2025 00:55:23 +0100 Subject: [PATCH 018/790] Refactor: Update Compose compiler configuration and build scripts This commit updates the Compose compiler configuration and makes minor adjustments to the build scripts. - In `instuctions`: - Added `./gradlew --rerun-tasks assembleFossReliantDebug -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true` command. - In `build-logic/convention/src/main/kotlin/com/mshdabiola/app/AndroidCompose.kt`: - Commented out one of the lines that added `compose_compiler_config.conf` to `stabilityConfigurationFiles`. - In `compose_compiler_config.conf`: - Replaced `com.mshdabiola.modules.model.data.*` with `com.mshdabiola.model.*`. - Added `kotlin.collections.*` to the list of stable classes. --- .../src/main/kotlin/com/mshdabiola/app/AndroidCompose.kt | 6 ++++-- compose_compiler_config.conf | 3 ++- instuctions | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/com/mshdabiola/app/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/com/mshdabiola/app/AndroidCompose.kt index b6fa1e82..e7525db8 100644 --- a/build-logic/convention/src/main/kotlin/com/mshdabiola/app/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/com/mshdabiola/app/AndroidCompose.kt @@ -51,7 +51,9 @@ internal fun Project.configureAndroidCompose( .relativeToRootProject("compose-reports") .let(reportsDestination::set) - stabilityConfigurationFiles - .add(isolated.rootProject.projectDirectory.file("compose_compiler_config.conf")) + stabilityConfigurationFiles.add( + isolated.rootProject.projectDirectory.file("compose_compiler_config.conf")) +// stabilityConfigurationFiles +// .add(isolated.rootProject.projectDirectory.file("compose_compiler_config.conf")) } } \ No newline at end of file diff --git a/compose_compiler_config.conf b/compose_compiler_config.conf index c957f3d8..9e70e8da 100644 --- a/compose_compiler_config.conf +++ b/compose_compiler_config.conf @@ -4,8 +4,9 @@ // We always use immutable classes for our data model, to avoid running the Compose compiler // in the module we declare it to be stable here. -com.mshdabiola.modules.model.data.* +com.mshdabiola.model.* // Java standard library classes java.time.ZoneId java.time.ZoneOffset +kotlin.collections.* diff --git a/instuctions b/instuctions index 2bd6fc9f..2c71f60b 100644 --- a/instuctions +++ b/instuctions @@ -20,4 +20,8 @@ sed -i -e '/androidx.dev/d' settings.gradle.kts sed -i -e '/with(target) {/,/^ }/d' -e '/CrashlyticsExtension/d' build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt - sed -i -e '/libs.firebase/d' build-logic/convention/build.gradle.kts \ No newline at end of file + sed -i -e '/libs.firebase/d' build-logic/convention/build.gradle.kts + + + + ./gradlew --rerun-tasks assembleFossReliantDebug -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true From c13afcd3aba970cac6998563fe9ad0001519095b Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Thu, 12 Jun 2025 01:46:28 +0100 Subject: [PATCH 019/790] Refactor: Manage selection state using `Set` instead of `NotePad.selected` This commit refactors how item selection is managed in the main feature. It replaces the `selected` boolean property in the `NotePad` model with a `setOfSelected` (a `Set`) in `MainState.Success`. This set stores the IDs of the selected items. **Key changes:** - **`feature/main/MainState.kt`**: - Added `setOfSelected: Set = emptySet()` to `MainState.Success` to track selected item IDs. - **`feature/main/MainScreen.kt`**: - Updated logic for `navigateToSelectLevel` to use `(mainState.value as MainState.Success).setOfSelected`. - Modified `noOfSelected` to be derived from `success.setOfSelected.size`. - Updated `isAllPin` to filter `notePads` based on whether their IDs are contained in `success.setOfSelected`. - The `LazyVerticalStaggeredGrid` items now receive the `setOfSelected` and pass the selection status to `NoteCard` via a new `isSelect` parameter. - **`feature/main/MainViewModel.kt`**: - `onSelectCard(id: Long)`: Now updates `_mainState.value` by adding or removing the `id` from the `setOfSelected` in `MainState.Success`. - `clearSelected()`: Resets `setOfSelected` to an empty set in `MainState.Success`. - Functions like `setPin()`, `setAlarm()`, `deleteAlarm()`, `setAllColor()`, `setAllArchive()`, and `setAllDelete()` now use the `setOfSelected` from `getSuccess()` to determine which notes to operate on. - `copyNote()`: Now retrieves the ID of the note to copy from `getSuccess().setOfSelected.first()`. - **`feature/main/TopbarAndDialog.kt`**: - `NoteCard` composable: - Added a new `isSelect: Boolean = false` parameter. - The border of the card is now determined by the `isSelect` parameter instead of `notePad.selected`. - Removed `selected = true` from the `NoteCardPreview`. - **`modules/model/NotePad.kt`**: - Removed the `selected: Boolean` property from the `NotePad` data class. --- .../kotlin/com/mshdabiola/main/MainScreen.kt | 14 ++++--- .../kotlin/com/mshdabiola/main/MainState.kt | 1 + .../com/mshdabiola/main/MainViewModel.kt | 38 ++++++++++--------- .../com/mshdabiola/main/TopbarAndDialog.kt | 5 ++- .../main/java/com/mshdabiola/model/NotePad.kt | 1 - 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt index c6b98ae9..aa911787 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt @@ -156,9 +156,7 @@ internal fun MainRoute( setAllAlarm = { showDialog = true }, setAllColor = { showColor = true }, setAllLabel = { - val selectId = - (mainState.value as MainState.Success).notePads.filter { it.selected }.map { it.id } - navigateToSelectLevel(selectId.toSet()) + navigateToSelectLevel((mainState.value as MainState.Success).setOfSelected) }, onCopy = mainViewModel::copyNote, onDelete = mainViewModel::setAllDelete, @@ -383,11 +381,11 @@ fun MainContent( } } - val noOfSelected = remember(success.notePads) { - success.notePads.count { it.selected } + val noOfSelected = remember(success.setOfSelected) { + success.setOfSelected.size } val isAllPin = remember(success.notePads) { - success.notePads.filter { it.selected } + success.notePads.filter { success.setOfSelected.contains(it.id) } .all { it.isPin } } var isGrid by rememberSaveable { mutableStateOf(true) } @@ -557,6 +555,7 @@ fun MainContent( items = pinNotePad.first.toImmutableList(), onNoteClick = onNoteClick, onSelectedCard = onSelectedCard, + setOfSelected = success.setOfSelected, ) if (pinNotePad.first.isNotEmpty() && pinNotePad.second.isNotEmpty()) { @@ -574,6 +573,7 @@ fun MainContent( items = pinNotePad.second.toImmutableList(), onNoteClick = onNoteClick, onSelectedCard = onSelectedCard, + setOfSelected = success.setOfSelected, ) } } @@ -585,6 +585,7 @@ fun LazyStaggeredGridScope.noteItems( sharedTransitionScope: SharedTransitionScope, animatedContentScope: AnimatedVisibilityScope, items: List, + setOfSelected: Set, onNoteClick: (Long) -> Unit, onSelectedCard: (Long) -> Unit, ) = items( @@ -600,6 +601,7 @@ fun LazyStaggeredGridScope.noteItems( animatedVisibilityScope = animatedContentScope, ), + isSelect = setOfSelected.contains(note.id), notePad = note, onCardClick = onNoteClick, onLongClick = onSelectedCard, diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt index 432d7bd8..d6f078fd 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt @@ -15,6 +15,7 @@ sealed class MainState { val label: List = emptyList(), val searchSort: SearchSort? = null, val mainData: MainData = MainData.Note, + val setOfSelected: Set = emptySet(), ) : MainState() diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt index 71a18f34..aa0150db 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt @@ -173,20 +173,16 @@ internal class MainViewModel * @param id The ID of the notepad card that was selected or deselected. */ fun onSelectCard(id: Long) { - val listNOtePad = getSuccess().notePads.toMutableList() - val index = listNOtePad.indexOfFirst { it.id == id } - val notepad = listNOtePad[index] - val newNotepad = notepad.copy(selected = !notepad.selected) - - listNOtePad[index] = newNotepad - - _mainState.value = getSuccess().copy(notePads = listNOtePad.toImmutableList()) + val selected = getSuccess().setOfSelected + if (selected.contains(id)) { + _mainState.value = getSuccess().copy(setOfSelected = selected - id) + } else { + _mainState.value = getSuccess().copy(setOfSelected = selected + id) + } } fun clearSelected() { - val listNOtePad = - getSuccess().notePads.map { it.copy(selected = false) } - _mainState.value = getSuccess().copy(notePads = listNOtePad.toImmutableList()) + _mainState.value = getSuccess().copy(setOfSelected = emptySet()) } fun setNoteType(noteType: NoteType) { @@ -194,8 +190,9 @@ internal class MainViewModel } fun setPin() { + val selected = getSuccess().setOfSelected val selectedNotepad = - getSuccess().notePads.filter { it.selected } + getSuccess().notePads.filter { selected.contains(it.id) } clearSelected() @@ -215,8 +212,9 @@ internal class MainViewModel } private fun setAlarm(time: Long, interval: Long?) { + val setOfSelected = getSuccess().setOfSelected val selectedNotes = - getSuccess().notePads.filter { it.selected } + getSuccess().notePads.filter { setOfSelected.contains(it.id) } clearSelected() val notes = selectedNotes.map { it.copy(reminder = time, interval = interval ?: -1) } @@ -240,8 +238,9 @@ internal class MainViewModel } fun deleteAlarm() { + val selected = getSuccess().setOfSelected val selectedNotes = - getSuccess().notePads.filter { it.selected } + getSuccess().notePads.filter { selected.contains(it.id) } clearSelected() val notes = selectedNotes.map { it.copy(reminder = -1, interval = -1) } @@ -258,8 +257,9 @@ internal class MainViewModel } fun setAllColor(colorId: Int) { + val selected = getSuccess().setOfSelected val selectedNotes = - getSuccess().notePads.filter { it.selected } + getSuccess().notePads.filter { selected.contains(it.id) } clearSelected() val notes = selectedNotes.map { it.copy(color = colorId) } @@ -270,8 +270,9 @@ internal class MainViewModel } fun setAllArchive() { + val selected = getSuccess().setOfSelected val selectedNotes = - getSuccess().notePads.filter { it.selected } + getSuccess().notePads.filter { selected.contains(it.id) } clearSelected() val notes = selectedNotes.map { it.copy(noteType = NoteType.ARCHIVE) } @@ -282,8 +283,9 @@ internal class MainViewModel } fun setAllDelete() { + val selected = getSuccess().setOfSelected val selectedNotes = - getSuccess().notePads.filter { it.selected } + getSuccess().notePads.filter { selected.contains(it.id) } clearSelected() val notes = selectedNotes.map { it.copy(noteType = NoteType.TRASH) } @@ -295,7 +297,7 @@ internal class MainViewModel fun copyNote() { viewModelScope.launch(Dispatchers.IO) { - val id = getSuccess().notePads.single { it.selected }.id + val id = getSuccess().setOfSelected.first() val notepads = notepadRepository.getOneNotePad(id).first() if (notepads != null) { diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt index f4f02553..098499d6 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/TopbarAndDialog.kt @@ -596,6 +596,7 @@ fun NoteBottomBar(modifier: Modifier = Modifier) { fun NoteCard( modifier: Modifier = Modifier, notePad: NotePad, + isSelect: Boolean = false, onCardClick: (Long) -> Unit = {}, onLongClick: (Long) -> Unit = {}, ) { @@ -638,7 +639,7 @@ fun NoteCard( onClick = { notePad.id.let { onCardClick(it) } }, onLongClick = { notePad.id.let { onLongClick(it) } }, ), - border = if (notePad.selected) { + border = if (isSelect) { BorderStroke(3.dp, Color.Blue) } else { BorderStroke( @@ -789,7 +790,7 @@ fun NoteCardPreview() { color = 2, isPin = false, background = 3, - selected = true, +// selected = true, // labels = listOf( // "ade", diff --git a/modules/model/src/main/java/com/mshdabiola/model/NotePad.kt b/modules/model/src/main/java/com/mshdabiola/model/NotePad.kt index 1e43c1ec..632940f0 100644 --- a/modules/model/src/main/java/com/mshdabiola/model/NotePad.kt +++ b/modules/model/src/main/java/com/mshdabiola/model/NotePad.kt @@ -15,7 +15,6 @@ data class NotePad( val editDateString: String = "Jul 3", val reminderString: String = "feb 1", val noteType: NoteType = NoteType.NOTE, - val selected: Boolean = false, val images: List = emptyList(), val voices: List = emptyList(), val checks: List = emptyList(), From 14567cfbbe0679ced1c737ea30f9ee493dadb081 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Thu, 12 Jun 2025 02:04:55 +0100 Subject: [PATCH 020/790] Refactor: Rename `setAllDelete` to `setAllToTrash` in MainViewModel This commit renames the function `setAllDelete` to `setAllToTrash` in `MainViewModel.kt` for clarity. - In `MainViewModel.kt`: - Renamed `setAllDelete()` to `setAllToTrash()`. - In `MainScreen.kt`: - Updated the call from `mainViewModel::setAllDelete` to `mainViewModel::setAllToTrash` in the `onDelete` lambda. --- feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt | 2 +- .../main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt index aa911787..7e4b5ec9 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt @@ -159,7 +159,7 @@ internal fun MainRoute( navigateToSelectLevel((mainState.value as MainState.Success).setOfSelected) }, onCopy = mainViewModel::copyNote, - onDelete = mainViewModel::setAllDelete, + onDelete = mainViewModel::setAllToTrash, onArchive = mainViewModel::setAllArchive, onSend = { mainViewModel.clearSelected() diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt index aa0150db..b2e45382 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt @@ -282,7 +282,7 @@ internal class MainViewModel } } - fun setAllDelete() { + fun setAllToTrash() { val selected = getSuccess().setOfSelected val selectedNotes = getSuccess().notePads.filter { selected.contains(it.id) } From d85a3791082239ed77eab5bbf66fad0afe037cc9 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 13:31:16 +0100 Subject: [PATCH 021/790] Refactor: Replace `BottomAppBar` with `HorizontalFloatingToolbar` in `NoteApp` This commit replaces the `BottomAppBar` with the experimental `HorizontalFloatingToolbar` for creating new notes in the `NoteApp`. - In `NoteApp.kt`: - Renamed `NoteBottomBar` composable to `NoteFloatingToolbar`. - Implemented `HorizontalFloatingToolbar` which includes a primary `FloatingActionButton` to toggle an expanded state revealing more options. - The main FAB now shows a `Cancel` icon when expanded and an `Add` icon when collapsed. - Removed the individual FAB for adding a new note; this action is now implicitly handled by the main FAB or the first icon in the expanded toolbar. - Updated the `Scaffold` to use `floatingActionButton` for the new `NoteFloatingToolbar` instead of `bottomBar`. - Added `@OptIn(ExperimentalMaterial3ExpressiveApi::class)` where `HorizontalFloatingToolbar` is used. - Added a preview for `NoteFloatingToolbar`. - Removed the disabled state visual cue for the voice input IconButton when voice support is unavailable. --- .../com/mshdabiola/playnotepad/ui/NoteApp.kt | 76 +++++++++++-------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt index bf43dd0c..34264a29 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt @@ -8,15 +8,17 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FloatingToolbarDefaults +import androidx.compose.material3.HorizontalFloatingToolbar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Scaffold @@ -29,6 +31,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -39,6 +42,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.mshdabiola.about.navigateToAbout @@ -135,9 +139,10 @@ fun NoteApp( contentColor = MaterialTheme.colorScheme.onBackground, contentWindowInsets = WindowInsets(0, 0, 0, 0), snackbarHost = { SnackbarHost(snackbarHostState) }, - bottomBar = { + floatingActionButton = { if (appState.isMain) { - NoteBottomBar( + NoteFloatingToolbar( + modifier = Modifier.navigationBarsPadding(), onAddNewNote = { appState.coroutineScope.launch { val id = viewModel.insertNewNote() @@ -240,8 +245,9 @@ private fun Modifier.notificationDot(): Modifier = } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable -fun NoteBottomBar( +fun NoteFloatingToolbar( modifier: Modifier = Modifier, onAddNewNote: () -> Unit = {}, onAddCheckNote: () -> Unit = {}, @@ -250,9 +256,34 @@ fun NoteBottomBar( onAddImageNote: () -> Unit = {}, isVoiceSupport: Boolean = false, ) { - BottomAppBar( + var expanded by rememberSaveable { mutableStateOf(false) } + val vibrantColors = FloatingToolbarDefaults.vibrantFloatingToolbarColors() + HorizontalFloatingToolbar( modifier = modifier, - actions = { + expanded = expanded, + floatingActionButton = { + FloatingToolbarDefaults.VibrantFloatingActionButton( + modifier = Modifier.testTag("main:add"), + onClick = { + expanded = !expanded + }, + ) { + Icon( + imageVector = if (expanded) NoteIcon.Cancel else NoteIcon.Add, + contentDescription = "add note", + ) + } + }, + colors = vibrantColors, + content = { + IconButton( + modifier = Modifier.testTag("main:note"), + onClick = onAddNewNote, + colors = IconButtonDefaults.filledIconButtonColors(), + ) { + Icon(imageVector = NoteIcon.Add, contentDescription = "add note") + } + IconButton( modifier = Modifier.testTag("main:check"), onClick = onAddCheckNote, @@ -283,18 +314,6 @@ fun NoteBottomBar( contentDescription = "add note voice", ) } - } else { - IconButton( - modifier = Modifier.testTag("main:voice"), - onClick = {}, // or show a tooltip - enabled = false, - ) { - Icon( - imageVector = NoteIcon.KeyboardVoice, - contentDescription = "add note voice (unavailable)", - tint = Color.Gray, // or other visual cue - ) - } } IconButton( @@ -307,15 +326,12 @@ fun NoteBottomBar( ) } }, - floatingActionButton = { - FloatingActionButton( - modifier = Modifier.testTag("main:add"), - onClick = onAddNewNote, - containerColor = MaterialTheme.colorScheme.primary, - elevation = FloatingActionButtonDefaults.elevation(), - ) { - Icon(imageVector = NoteIcon.Add, contentDescription = "add note") - } - }, ) } + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Preview +@Composable +fun NoteBottomBarPreview() { + NoteFloatingToolbar() +} From 3c16e86bb73c61007b5729f001c28f49e82b415a Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 13:31:47 +0100 Subject: [PATCH 022/790] Refactor: Update NavigationTest to include click on note type This commit updates the `onAddButton_showDetails` test in `NavigationTest.kt`. - The test now simulates a click on a note type (e.g., "main:note") after clicking the add button. - The assertion for the detail title (`detail:title`) is currently commented out. - Added `@OptIn(ExperimentalTestApi::class)` annotation to the test method. --- .../java/com/mshdabiola/playnotepad/NavigationTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/androidTest/java/com/mshdabiola/playnotepad/NavigationTest.kt b/app/src/androidTest/java/com/mshdabiola/playnotepad/NavigationTest.kt index ffca3f4a..b46bbe01 100644 --- a/app/src/androidTest/java/com/mshdabiola/playnotepad/NavigationTest.kt +++ b/app/src/androidTest/java/com/mshdabiola/playnotepad/NavigationTest.kt @@ -4,6 +4,7 @@ package com.mshdabiola.playnotepad +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick @@ -61,11 +62,15 @@ class NavigationTest { } } + @OptIn(ExperimentalTestApi::class) @Test fun onAddButton_showDetails() { composeTestRule.apply { // GIVEN the user follows a topic onNodeWithTag("main:add").performClick() + onNodeWithTag("main:note").performClick() + +// v onNodeWithTag("detail:title").assertExists() // onNodeWithTag("detail:title").assertExists() } From 999b991c298e02c54da9c84bee5a14ae0db56c33 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 13:32:04 +0100 Subject: [PATCH 023/790] Refactor: Update `NoteVoicePlayer` to use `LinearWavyProgressIndicator` This commit updates the `NoteVoicePlayer` composable in `DetailScreen.kt` to use the `LinearWavyProgressIndicator` from Material 3 Expressive instead of the standard `LinearProgressIndicator`. - In `feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt`: - Imported `ExperimentalMaterial3ExpressiveApi` and `LinearWavyProgressIndicator`. - Annotated `NoteVoicePlayer` with `@OptIn(ExperimentalMaterial3ExpressiveApi::class)`. - Replaced `LinearProgressIndicator` with `LinearWavyProgressIndicator` for displaying voice playback progress. --- .../src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt index 6c1eae14..6c8b4fd4 100644 --- a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt +++ b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt @@ -41,9 +41,10 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LinearWavyProgressIndicator import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme @@ -789,6 +790,7 @@ fun NoteCheck( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun NoteVoicePlayer( noteVoiceUiState: NoteVoice, @@ -814,7 +816,7 @@ fun NoteVoicePlayer( } } } - LinearProgressIndicator( + LinearWavyProgressIndicator( progress = { (noteVoiceUiState.currentProgress.toFloat() / noteVoiceUiState.length) }, modifier = Modifier.weight(1f), ) From b703b420b3683fe769c7b9b86c1fe8b97cb12d97 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 13:32:15 +0100 Subject: [PATCH 024/790] Refactor: Update `Cancel` icon from `Icons.Outlined.Cancel` to `Icons.Outlined.Close` This commit changes the `Cancel` icon in `NoteIcon.kt`. - In `modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt`: - Replaced `androidx.compose.material.icons.outlined.Cancel` with `androidx.compose.material.icons.outlined.Close`. - Updated `NoteIcon.Cancel` to use `Icons.Outlined.Close`. --- .../main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt index 0ac203a5..3717e7ba 100644 --- a/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt +++ b/modules/designsystem/src/main/kotlin/com/mshdabiola/designsystem/icon/NoteIcon.kt @@ -12,10 +12,10 @@ import androidx.compose.material.icons.outlined.AddBox import androidx.compose.material.icons.outlined.Alarm import androidx.compose.material.icons.outlined.Archive import androidx.compose.material.icons.outlined.Brush -import androidx.compose.material.icons.outlined.Cancel import androidx.compose.material.icons.outlined.CheckBox import androidx.compose.material.icons.outlined.CheckBoxOutlineBlank import androidx.compose.material.icons.outlined.Clear +import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.ColorLens import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.Delete @@ -78,7 +78,7 @@ object NoteIcon { val CheckBox = Icons.Outlined.CheckBox val Image = Icons.Outlined.Image val KeyboardVoice = Icons.Outlined.KeyboardVoice - val Cancel = Icons.Outlined.Cancel + val Cancel = Icons.Outlined.Close val ArrowBack = Icons.AutoMirrored.Outlined.ArrowBack val MoreVert = Icons.Outlined.MoreVert val Search = Icons.Outlined.Search From 447cd933b3057527d7cdf003db72738dd3d40093 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 14:01:06 +0100 Subject: [PATCH 025/790] Refactor: Update `getOneNotePad` in test repositories This commit updates the `getOneNotePad` function in both `TestNotePadRepository` and `FakeNotePadRepository` to return a `NotePad` object with the provided `id`. - In `TestNotePadRepository.kt`: - Modified `getOneNotePad(id: Long)` to return `flow { NotePad(id = id) }`. - Added a `println` statement for debugging. - In `FakeNotePadRepository.kt`: - Modified `getOneNotePad(id: Long)` to return `flow { NotePad(id = id) }`. - Added a `println` statement for debugging. --- .../testing/fake/repository/FakeNotePadRepository.kt | 3 ++- .../mshdabiola/testing/repository/TestNotePadRepository.kt | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt b/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt index 412aa076..6d19dc8a 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt @@ -32,7 +32,8 @@ class FakeNotePadRepository @Inject constructor() : INotePadRepository { } override fun getOneNotePad(id: Long): Flow { - return flow { NotePad() } + println("id2 $id") + return flow { NotePad(id = id) } } override suspend fun deleteTrashType() { diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt b/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt index 0eddaf35..f73e906e 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt @@ -4,6 +4,7 @@ import com.mshdabiola.data.repository.INotePadRepository import com.mshdabiola.model.NotePad import com.mshdabiola.model.NoteType import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @@ -33,7 +34,9 @@ class TestNotePadRepository : INotePadRepository { } override fun getOneNotePad(id: Long): Flow { - TODO("Not yet implemented") + println("id1 $id") + + return flow { NotePad(id = id) } } override suspend fun deleteTrashType() { From 4cc8737e0666a5b05cd9a7d17cd6ed4ef339a396 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:29:09 +0100 Subject: [PATCH 026/790] Refactor: Update `setMainData` and `getNotePads` to use `MainData` enum This commit modifies the `setMainData` and `getNotePads` (renamed to `getNotePadsWithMainData`) functions in user data and notepad repositories to use the `MainData` enum instead of a `Long` or `NoteType`. - In `FakeUserDataRepository.kt`: - Changed the parameter type of `setMainData` from `Long` to `MainData`. - In `TestNotePadRepository.kt`: - Renamed `getNotePads` to `getNotePadsWithMainData`. - Changed the parameter type from `NoteType` to `MainData`. - In `FakeNotePadRepository.kt`: - Renamed `getNotePads` to `getNotePadsWithMainData`. - Changed the parameter type from `NoteType` to `MainData`. - The implementation now throws `NotImplementedError`. - In `TestUserDataRepository.kt`: - Changed the parameter type of `setMainData` from `Long` to `MainData`. - Updated the implementation to use `MainData.Note` when setting the main data in `currentUserData`. --- .../testing/fake/repository/FakeNotePadRepository.kt | 6 +++--- .../testing/fake/repository/FakeUserDataRepository.kt | 3 ++- .../mshdabiola/testing/repository/TestNotePadRepository.kt | 4 ++-- .../mshdabiola/testing/repository/TestUserDataRepository.kt | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt b/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt index 412aa076..a2c11179 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeNotePadRepository.kt @@ -1,8 +1,8 @@ package com.mshdabiola.testing.fake.repository import com.mshdabiola.data.repository.INotePadRepository +import com.mshdabiola.model.MainData import com.mshdabiola.model.NotePad -import com.mshdabiola.model.NoteType import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.datetime.LocalDate @@ -23,8 +23,8 @@ class FakeNotePadRepository @Inject constructor() : INotePadRepository { override suspend fun deleteNoteCheckByNoteId(noteId: Long) { } - override fun getNotePads(noteType: NoteType): Flow> { - return flow { emptyList() } + override fun getNotePadsWithMainData(mainData: MainData): Flow> { + TODO("Not yet implemented") } override fun getNotePads(): Flow> { diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeUserDataRepository.kt b/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeUserDataRepository.kt index 0ee979e7..17c66930 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeUserDataRepository.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/fake/repository/FakeUserDataRepository.kt @@ -8,6 +8,7 @@ import com.mshdabiola.data.repository.UserDataRepository import com.mshdabiola.datastore.UserPreferencesRepository import com.mshdabiola.model.Contrast import com.mshdabiola.model.DarkThemeConfig +import com.mshdabiola.model.MainData import com.mshdabiola.model.ThemeBrand import com.mshdabiola.model.UserData import kotlinx.coroutines.flow.Flow @@ -46,7 +47,7 @@ class FakeUserDataRepository @Inject constructor( userPreferencesRepository.setShouldHideOnboarding(shouldHideOnboarding) } - override suspend fun setMainData(mainData: Long) { + override suspend fun setMainData(mainData: MainData) { userPreferencesRepository.setMainData(mainData) } } diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt b/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt index 0eddaf35..52b09b88 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestNotePadRepository.kt @@ -1,8 +1,8 @@ package com.mshdabiola.testing.repository import com.mshdabiola.data.repository.INotePadRepository +import com.mshdabiola.model.MainData import com.mshdabiola.model.NotePad -import com.mshdabiola.model.NoteType import kotlinx.coroutines.flow.Flow import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @@ -24,7 +24,7 @@ class TestNotePadRepository : INotePadRepository { TODO("Not yet implemented") } - override fun getNotePads(noteType: NoteType): Flow> { + override fun getNotePadsWithMainData(mainData: MainData): Flow> { TODO("Not yet implemented") } diff --git a/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestUserDataRepository.kt b/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestUserDataRepository.kt index 83fa144f..f4724c46 100644 --- a/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestUserDataRepository.kt +++ b/modules/testing/src/main/java/com/mshdabiola/testing/repository/TestUserDataRepository.kt @@ -61,7 +61,7 @@ class TestUserDataRepository : UserDataRepository { } } - override suspend fun setMainData(mainData: Long) { + override suspend fun setMainData(mainData: MainData) { currentUserData.let { current -> _userData.tryEmit(current.copy(mainData = MainData.Note)) } From d750ceb11ca3ab014532e29b7ad1f8e27eae384c Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:29:38 +0100 Subject: [PATCH 027/790] Refactor: Simplify `MainData` to a data class and introduce `NoteType` This commit refactors the `MainData` sealed class into a simple data class. It introduces a `noteType` property that derives its value from the `index` property. This change aims to simplify the structure and improve the readability of how different main data types are handled. - **`modules/model/src/main/java/com/mshdabiola/model/MainData.kt`**: - Changed `MainData` from a `sealed class` to a `data class` with a default `index` value corresponding to `NoteType.NOTE`. - Removed the individual object/data class declarations for `Note`, `Achieve`, `Trash`, `Label`, and `Remainder`. - Added a `noteType` read-only property of type `NoteType` (enum assumed to be defined elsewhere) which is determined by the value of `index`. - `index == -1L` maps to `NoteType.NOTE` - `index == -2L` maps to `NoteType.ARCHIVE` - `index == -3L` maps to `NoteType.TRASH` - `index == -4L` maps to `NoteType.REMAINDER` - Any other `index` value maps to `NoteType.LABEL` --- .../main/java/com/mshdabiola/model/MainData.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/model/src/main/java/com/mshdabiola/model/MainData.kt b/modules/model/src/main/java/com/mshdabiola/model/MainData.kt index 4431ff9e..ec5895a3 100644 --- a/modules/model/src/main/java/com/mshdabiola/model/MainData.kt +++ b/modules/model/src/main/java/com/mshdabiola/model/MainData.kt @@ -1,9 +1,12 @@ package com.mshdabiola.model -sealed class MainData(val index: Long) { - object Note : MainData(-1L) - object Achieve : MainData(-2L) - object Trash : MainData(-3L) - data class Label(val id: Long) : MainData(index = id) // Assigning a default index like in the original enum - object Remainder : MainData(-4L) +data class MainData(val index: Long = NoteType.NOTE.index) { + val noteType: NoteType + get() = when (index) { + -1L -> NoteType.NOTE + -2L -> NoteType.ARCHIVE + -3L -> NoteType.TRASH + -4L -> NoteType.REMAINDER + else -> NoteType.LABEL + } } From df5d2c99b1ee54ae23a65eefa8ada2f5b65556ad Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:29:49 +0100 Subject: [PATCH 028/790] Feat: Add DateUseCase and update domain module dependencies This commit introduces a new `DateUseCase` class in the domain module and updates its dependencies. - In `modules/domain/src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt`: - Added a new class `DateUseCase` with an invoke operator that currently returns an empty string. - In `modules/domain/build.gradle.kts`: - Added `api` dependencies for `projects.modules.data` and `projects.modules.model`. - Added `testImplementation` dependency for `projects.modules.testing`. --- modules/domain/build.gradle.kts | 7 +++++++ .../src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 modules/domain/src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt diff --git a/modules/domain/build.gradle.kts b/modules/domain/build.gradle.kts index dec052fc..70185652 100644 --- a/modules/domain/build.gradle.kts +++ b/modules/domain/build.gradle.kts @@ -9,4 +9,11 @@ plugins { android { namespace = "com.mshdabiola.domain" +} +dependencies { + api(projects.modules.data) + api(projects.modules.model) + + + testImplementation(projects.modules.testing) } \ No newline at end of file diff --git a/modules/domain/src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt b/modules/domain/src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt new file mode 100644 index 00000000..8940058e --- /dev/null +++ b/modules/domain/src/main/kotlin/com/mshdabiola/domain/DateUseCase.kt @@ -0,0 +1,7 @@ +package com.mshdabiola.domain + +class DateUseCase { + operator fun invoke(limit: Int = 10): String { + return "" + } +} From a0caac6aceff219edfed30f89a1aecdec1bd4dac Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:29:58 +0100 Subject: [PATCH 029/790] Refactor: Simplify `MainData` handling in `UserPreferencesRepository` This commit refactors how `MainData` is handled in `UserPreferencesRepository.kt`. - The mapping of `it.mainScreenType` to `MainData` enum values has been replaced with a direct instantiation of `MainData(it.mainScreenType)`. - The `setMainData` function now accepts a `MainData` object as a parameter instead of a `Long` index. The `mainScreenType` in `UserPreferences` is updated using `mainData.index`. --- .../datastore/UserPreferencesRepository.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modules/datastore/src/main/kotlin/com/mshdabiola/datastore/UserPreferencesRepository.kt b/modules/datastore/src/main/kotlin/com/mshdabiola/datastore/UserPreferencesRepository.kt index c3665404..1a4a30e8 100644 --- a/modules/datastore/src/main/kotlin/com/mshdabiola/datastore/UserPreferencesRepository.kt +++ b/modules/datastore/src/main/kotlin/com/mshdabiola/datastore/UserPreferencesRepository.kt @@ -54,13 +54,7 @@ class UserPreferencesRepository @Inject constructor( ThemeContrastProto.THEME_CONTRAST_MEDIUM -> Contrast.Medium }, - mainData = when (it.mainScreenType) { - MainData.Note.index -> MainData.Note - MainData.Achieve.index -> MainData.Achieve - MainData.Trash.index -> MainData.Trash - MainData.Remainder.index -> MainData.Remainder - else -> MainData.Label(it.mainScreenType) - }, + mainData = MainData(it.mainScreenType), ) } @@ -112,9 +106,9 @@ class UserPreferencesRepository @Inject constructor( it.copy { this.shouldHideOnboarding = shouldHideOnboarding } } } - suspend fun setMainData(index: Long) { + suspend fun setMainData(mainData: MainData) { userPreferences.updateData { - it.copy { this.mainScreenType = index } + it.copy { this.mainScreenType = mainData.index } } } } From 0e244055b2e4eb31f5d014f89a3b1ed31083d127 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:30:09 +0100 Subject: [PATCH 030/790] Refactor: Update `NotepadDao` to include `getListOfNotePadByReminder` This commit updates `NotepadDao.kt` to enhance functionality for retrieving notes. - Renamed `getListOfNotePad` to `getListOfNotePadByNoteType` for clarity. - Added a new function `getListOfNotePadByReminder` to fetch notes that have a reminder set (`reminder > 0`), ordered by ID in descending order. --- .../src/main/java/com/mshdabiola/database/dao/NotepadDao.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/database/src/main/java/com/mshdabiola/database/dao/NotepadDao.kt b/modules/database/src/main/java/com/mshdabiola/database/dao/NotepadDao.kt index d8772924..7aee3f2d 100644 --- a/modules/database/src/main/java/com/mshdabiola/database/dao/NotepadDao.kt +++ b/modules/database/src/main/java/com/mshdabiola/database/dao/NotepadDao.kt @@ -11,7 +11,11 @@ import kotlinx.coroutines.flow.Flow interface NotepadDao { @Transaction @Query("SELECT * FROM note_table WHERE noteType = :noteType ORDER BY id DESC") - fun getListOfNotePad(noteType: NoteType): Flow> + fun getListOfNotePadByNoteType(noteType: NoteType): Flow> + + @Transaction + @Query("SELECT * FROM note_table WHERE reminder > 0 ORDER BY id DESC") + fun getListOfNotePadByReminder(): Flow> @Transaction @Query("SELECT * FROM note_table ORDER BY id DESC") From 6cbdd660b50536bf301db20da2dfb8be88a8c45e Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:30:20 +0100 Subject: [PATCH 031/790] Refactor: Update NotePadRepository and UserDataRepository to use `MainData` This commit updates `NotePadRepository` and `UserDataRepository` to utilize the `MainData` model class for managing note types and user preferences, enhancing type safety and clarity. **Key changes:** - **`NotePadRepository.kt`**: - Replaced `getNotePads(noteType: NoteType)` with `getNotePadsWithMainData(mainData: MainData)`. - The new method `getNotePadsWithMainData` now filters notes based on `mainData.noteType`. - For `NoteType.LABEL`, it filters notes where any label ID matches `mainData.index`. - For `NoteType.REMAINDER`, it fetches notes by reminder. - For other `NoteType`s, it fetches notes by `mainData.noteType`. - Updated `deleteTrashType()` to use `getNotePadsWithMainData(MainData(NoteType.TRASH.index))` to fetch trash items. - **`INotePadRepository.kt`**: - Updated the interface to reflect the change from `getNotePads(noteType: NoteType)` to `getNotePadsWithMainData(mainData: MainData)`. - **`UserDataRepository.kt`**: - Modified `setMainData(mainData: Long)` to `setMainData(mainData: MainData)`. - **`OfflineFirstUserDataRepository.kt`**: - Updated the implementation of `setMainData` to accept a `MainData` object. --- .../data/repository/INotePadRepository.kt | 5 +-- .../data/repository/NotePadRepository.kt | 31 ++++++++++++++++--- .../OfflineFirstUserDataRepository.kt | 3 +- .../data/repository/UserDataRepository.kt | 3 +- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/INotePadRepository.kt b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/INotePadRepository.kt index 417c7701..bc4ee004 100644 --- a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/INotePadRepository.kt +++ b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/INotePadRepository.kt @@ -1,7 +1,7 @@ package com.mshdabiola.data.repository +import com.mshdabiola.model.MainData import com.mshdabiola.model.NotePad -import com.mshdabiola.model.NoteType import kotlinx.coroutines.flow.Flow import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalTime @@ -14,7 +14,8 @@ interface INotePadRepository { suspend fun deleteCheckNote(id: Long, noteId: Long) suspend fun deleteNoteCheckByNoteId(noteId: Long) - fun getNotePads(noteType: NoteType): Flow> + + fun getNotePadsWithMainData(mainData: MainData): Flow> fun getNotePads(): Flow> // fun getNote() = generalDao.getNote().map { noteEntities -> noteEntities.map { it.toNote() } } diff --git a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/NotePadRepository.kt b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/NotePadRepository.kt index 346b198c..6e746aa8 100644 --- a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/NotePadRepository.kt +++ b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/NotePadRepository.kt @@ -16,11 +16,13 @@ import com.mshdabiola.database.dao.NoteVoiceDao import com.mshdabiola.database.dao.NotepadDao import com.mshdabiola.database.dao.PathDao import com.mshdabiola.database.model.NoteLabelEntity +import com.mshdabiola.model.MainData import com.mshdabiola.model.NotePad import com.mshdabiola.model.NoteType import com.mshdabiola.model.NoteUri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -76,9 +78,30 @@ internal class NotePadRepository noteCheckDao.deleteByNoteId(noteId) } - override fun getNotePads(noteType: NoteType) = notePadDao - .getListOfNotePad(noteType) - .map { entities -> entities.map { transform(it.toNotePad()) } } + override fun getNotePadsWithMainData(mainData: MainData): Flow> { + return when (mainData.noteType) { + NoteType.LABEL -> { + notePadDao.getListOfNotePad() + .map { + it.filter { + it.labels + .any { it.label.id == mainData.index } + } + } + } + + NoteType.REMAINDER -> { + notePadDao.getListOfNotePadByReminder() + } + + else -> notePadDao.getListOfNotePadByNoteType(mainData.noteType) + } + .map { entities -> entities.map { transform(it.toNotePad()) } } + } +// +// override fun getNotePads(noteType: NoteType) = notePadDao +// .getListOfNotePad(noteType) +// .map { entities -> entities.map { transform(it.toNotePad()) } } override fun getNotePads() = notePadDao .getListOfNotePad().map { entities -> entities.map { transform(it.toNotePad()) } } @@ -97,7 +120,7 @@ internal class NotePadRepository } override suspend fun deleteTrashType() = withContext(Dispatchers.IO) { - val list = getNotePads(NoteType.TRASH).first() + val list = getNotePadsWithMainData(MainData(NoteType.TRASH.index)).first() delete(list) } diff --git a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/OfflineFirstUserDataRepository.kt b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/OfflineFirstUserDataRepository.kt index ed22518e..83f5119c 100644 --- a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/OfflineFirstUserDataRepository.kt +++ b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/OfflineFirstUserDataRepository.kt @@ -8,6 +8,7 @@ import com.mshdabiola.analytics.AnalyticsHelper import com.mshdabiola.datastore.UserPreferencesRepository import com.mshdabiola.model.Contrast import com.mshdabiola.model.DarkThemeConfig +import com.mshdabiola.model.MainData import com.mshdabiola.model.ThemeBrand import com.mshdabiola.model.UserData import kotlinx.coroutines.flow.Flow @@ -46,7 +47,7 @@ internal class OfflineFirstUserDataRepository @Inject constructor( analyticsHelper.logOnboardingStateChanged(shouldHideOnboarding) } - override suspend fun setMainData(mainData: Long) { + override suspend fun setMainData(mainData: MainData) { userPreferencesRepository.setMainData(mainData) } } diff --git a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/UserDataRepository.kt b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/UserDataRepository.kt index 1a91e468..be446d5a 100644 --- a/modules/data/src/main/kotlin/com/mshdabiola/data/repository/UserDataRepository.kt +++ b/modules/data/src/main/kotlin/com/mshdabiola/data/repository/UserDataRepository.kt @@ -6,6 +6,7 @@ package com.mshdabiola.data.repository import com.mshdabiola.model.Contrast import com.mshdabiola.model.DarkThemeConfig +import com.mshdabiola.model.MainData import com.mshdabiola.model.ThemeBrand import com.mshdabiola.model.UserData import kotlinx.coroutines.flow.Flow @@ -35,5 +36,5 @@ interface UserDataRepository { * Sets whether the user has completed the onboarding process. */ suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) - suspend fun setMainData(mainData: Long) + suspend fun setMainData(mainData: MainData) } From 2e1516c0507a9142dce996d78d796685610f9461 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:30:45 +0100 Subject: [PATCH 032/790] Refactor: Simplify MainState and remove search functionality This commit refactors the `MainState` and related logic in `MainViewModel` and `MainScreen`. The primary change is the removal of search-specific properties and logic from `MainState` and the direct handling of notepad filtering based on `MainData` within `MainViewModel`. **Key changes:** - **`feature/main/MainState.kt`**: - Removed `isSearch`, `noteType`, `types`, `color`, `label`, and `searchSort` properties from `MainState.Success`. - Removed `Empty` and `Finish` sealed class instances. - The `mainData` property in `MainState.Success` now defaults to `MainData()` instead of `MainData.Note`. - **`feature/main/MainScreen.kt`**: - Removed parameters related to search functionality: `searchState`, `toggleSearch`, and `onSetSearch` from `MainRoute`, `MainScreen`, and `SuccessState` composables. - Removed `SearchTopBar` usage. - `MainTopAppBar`'s `navigateToSearch` parameter is now a no-op lambda. - Removed the logic for displaying search filter options (Types, Labels, Colors) when `isSearch` was true and `notePads` were empty. - `EmptyState` now takes `mainData: MainData` instead of `noteType: NoteType`. - The condition for displaying `EmptyState` in `SuccessState` now checks `success.notePads.isEmpty()` instead of `!success.isSearch && success.notePads.isEmpty()`. - TopAppBar selection logic (`LabelTopAppBar`, `MainTopAppBar`, etc.) now directly uses `success.mainData.noteType` instead of `success.noteType`. - **`feature/main/MainViewModel.kt`**: - Removed `searchState` (`TextFieldState`). - The `mainState` is now a `StateFlow` derived from combining `currentNotepads` (filtered by `MainData` from `userDataRepository`), `userDataRepository.userData.mainData`, and `setOfSelected` (a new `MutableStateFlow>`). This simplifies state management by removing the complex `combine` logic that handled search and notepad filtering. - Removed `toggleSearch` and `onSetSearch` functions. - `onSelectCard` and `clearSelected` now update the `setOfSelected` `MutableStateFlow` directly. - `setNoteType` function's body is cleared as `noteType` is no longer part of `MainState`. - Removed the initial `viewModelScope.launch` blocks that were collecting `userData` and combining `mainState`, `searchState.text`, and `notepadRepository.getNotePads()` to update `_mainState`. The new `mainState` flow handles this reactively. --- .../kotlin/com/mshdabiola/main/MainScreen.kt | 160 +++++------ .../kotlin/com/mshdabiola/main/MainState.kt | 17 +- .../com/mshdabiola/main/MainViewModel.kt | 262 ++++-------------- 3 files changed, 121 insertions(+), 318 deletions(-) diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt index 7e4b5ec9..da889660 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainScreen.kt @@ -8,7 +8,6 @@ import ArchiveTopAppBar import LabelTopAppBar import MainTopAppBar import NoteCard -import SearchTopBar import SelectTopBar import TrashTopAppBar import androidx.compose.animation.AnimatedVisibility @@ -37,7 +36,6 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api @@ -78,6 +76,7 @@ import com.mshdabiola.analytics.LocalAnalyticsHelper import com.mshdabiola.common.result.Result import com.mshdabiola.designsystem.component.NoteLoadingWheel import com.mshdabiola.designsystem.icon.NoteIcon +import com.mshdabiola.model.MainData import com.mshdabiola.model.Note import com.mshdabiola.model.NotePad import com.mshdabiola.model.NoteType @@ -149,7 +148,6 @@ internal fun MainRoute( modifier = modifier, mainState = mainState.value, navigateToEdit = navigateToDetail, - searchState = mainViewModel.searchState, onSelectedCard = mainViewModel::onSelectCard, onClearSelected = mainViewModel::clearSelected, setAllPin = mainViewModel::setPin, @@ -168,8 +166,6 @@ internal fun MainRoute( onDeleteLabel = { showDeleteLabel = true }, onEmptyTrash = mainViewModel::emptyTrash, onOpenDrawer = onOpenDrawer, - toggleSearch = mainViewModel::toggleSearch, - onSetSearch = mainViewModel::onSetSearch, // items = timeline, ) @@ -230,7 +226,6 @@ internal fun MainScreen( sharedTransitionScope: SharedTransitionScope, animatedContentScope: AnimatedVisibilityScope, mainState: MainState, - searchState: TextFieldState, navigateToEdit: (Long) -> Unit = {}, onOpenDrawer: () -> Unit = {}, onSelectedCard: (Long) -> Unit = {}, @@ -246,8 +241,6 @@ internal fun MainScreen( onRenameLabel: () -> Unit = {}, onDeleteLabel: () -> Unit = {}, onEmptyTrash: () -> Unit = {}, - toggleSearch: () -> Unit = {}, - onSetSearch: (SearchSort?) -> Unit = {}, ) { val state = rememberLazyListState() TrackScrollJank(scrollableState = state, stateName = "topic:screen") @@ -259,7 +252,6 @@ internal fun MainScreen( sharedTransitionScope = sharedTransitionScope, animatedContentScope = animatedContentScope, success = mainState, - searchState = searchState, navigateToEdit = navigateToEdit, onSelectedCard = onSelectedCard, onClearSelected = onClearSelected, @@ -275,20 +267,12 @@ internal fun MainScreen( onDeleteLabel = onDeleteLabel, onEmptyTrash = onEmptyTrash, onOpenDrawer = onOpenDrawer, - toggleSearch = toggleSearch, - onSetSearch = onSetSearch, ) } is MainState.Loading -> { LoadingState() } - - is MainState.Empty -> { - EmptyState() - } - - is MainState.Finish -> {} } // with(sharedTransitionScope) { @@ -317,7 +301,7 @@ private fun LoadingState(modifier: Modifier = Modifier) { } @Composable -private fun EmptyState(modifier: Modifier = Modifier, noteType: NoteType = NoteType.NOTE) { +private fun EmptyState(modifier: Modifier = Modifier, mainData: MainData = MainData()) { Column( modifier = modifier .padding(16.dp) @@ -353,7 +337,6 @@ fun MainContent( sharedTransitionScope: SharedTransitionScope, animatedContentScope: AnimatedVisibilityScope, success: MainState.Success, - searchState: TextFieldState, navigateToEdit: (Long) -> Unit = {}, onSelectedCard: (Long) -> Unit = {}, onClearSelected: () -> Unit = {}, @@ -369,8 +352,6 @@ fun MainContent( onDeleteLabel: () -> Unit = {}, onEmptyTrash: () -> Unit = {}, onOpenDrawer: () -> Unit = {}, - toggleSearch: () -> Unit = {}, - onSetSearch: (SearchSort?) -> Unit, // ={} ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val pinScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() @@ -420,7 +401,7 @@ fun MainContent( ) } - success.noteType == NoteType.LABEL -> { + success.mainData.noteType == NoteType.LABEL -> { LabelTopAppBar( label = "Label Name", // labels.single { it.id == currentNoteType.id }.label, onNavigate = { }, @@ -430,24 +411,17 @@ fun MainContent( ) } - success.noteType == NoteType.NOTE -> { - if (success.isSearch) { - SearchTopBar( - state = searchState, - toggleSearch = toggleSearch, - ) - } else { - MainTopAppBar( - onNavigate = onOpenDrawer, - scrollBehavior = scrollBehavior, - isGrid = isGrid, - navigateToSearch = toggleSearch, - onToggleGrid = { isGrid = !isGrid }, - ) - } + success.mainData.noteType == NoteType.NOTE -> { + MainTopAppBar( + onNavigate = onOpenDrawer, + scrollBehavior = scrollBehavior, + isGrid = isGrid, + navigateToSearch = {}, + onToggleGrid = { isGrid = !isGrid }, + ) } - success.noteType == NoteType.TRASH -> { + success.mainData.noteType == NoteType.TRASH -> { TrashTopAppBar( onNavigate = { }, scrollBehavior = scrollBehavior, @@ -455,7 +429,7 @@ fun MainContent( ) } - success.noteType == NoteType.REMAINDER -> { + success.mainData.noteType == NoteType.REMAINDER -> { ArchiveTopAppBar( name = "Remainder", onNavigate = { }, @@ -464,7 +438,7 @@ fun MainContent( ) } - success.noteType == NoteType.ARCHIVE -> { + success.mainData.noteType == NoteType.ARCHIVE -> { ArchiveTopAppBar( onNavigate = { }, scrollBehavior = scrollBehavior, @@ -484,60 +458,60 @@ fun MainContent( verticalItemSpacing = 8.dp, ) { - if (success.isSearch && success.notePads.isEmpty()) { - item(span = StaggeredGridItemSpan.FullLine) { - LabelBox( - title = stringResource(Rd.string.modules_designsystem_types), - success.types, - onItemClick = onSetSearch, - ) - } - - item(span = StaggeredGridItemSpan.FullLine) { - LabelBox( - title = stringResource(Rd.string.modules_designsystem_labels), - success.label, - onItemClick = onSetSearch, - ) - } - item(span = StaggeredGridItemSpan.FullLine) { - Text(text = stringResource(Rd.string.modules_designsystem_colors)) - } - - item(span = StaggeredGridItemSpan.FullLine) { - FlowRow( - verticalArrangement = Arrangement.spacedBy(4.dp), - - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - success.color.forEach { - Surface( - onClick = { - onSetSearch(it) - }, - shape = CircleShape, - color = if (it.colorIndex == -1) Color.White else NoteIcon.noteColors[it.colorIndex], - modifier = Modifier - .width(40.dp) - .aspectRatio(1f), - - ) { - if (it.colorIndex == -1) { - Icon( - imageVector = NoteIcon.FormatColorReset, - contentDescription = "done", - tint = Color.Gray, - modifier = Modifier.padding(4.dp), - ) - } - } - } - } - } - } - if (!success.isSearch && success.notePads.isEmpty()) { +// if (success.isSearch && success.notePads.isEmpty()) { +// item(span = StaggeredGridItemSpan.FullLine) { +// LabelBox( +// title = stringResource(Rd.string.modules_designsystem_types), +// success.types, +// onItemClick = onSetSearch, +// ) +// } +// +// item(span = StaggeredGridItemSpan.FullLine) { +// LabelBox( +// title = stringResource(Rd.string.modules_designsystem_labels), +// success.label, +// onItemClick = onSetSearch, +// ) +// } +// item(span = StaggeredGridItemSpan.FullLine) { +// Text(text = stringResource(Rd.string.modules_designsystem_colors)) +// } +// +// item(span = StaggeredGridItemSpan.FullLine) { +// FlowRow( +// verticalArrangement = Arrangement.spacedBy(4.dp), +// +// horizontalArrangement = Arrangement.spacedBy(4.dp), +// ) { +// success.color.forEach { +// Surface( +// onClick = { +// onSetSearch(it) +// }, +// shape = CircleShape, +// color = if (it.colorIndex == -1) Color.White else NoteIcon.noteColors[it.colorIndex], +// modifier = Modifier +// .width(40.dp) +// .aspectRatio(1f), +// +// ) { +// if (it.colorIndex == -1) { +// Icon( +// imageVector = NoteIcon.FormatColorReset, +// contentDescription = "done", +// tint = Color.Gray, +// modifier = Modifier.padding(4.dp), +// ) +// } +// } +// } +// } +// } +// } + if (success.notePads.isEmpty()) { item(span = StaggeredGridItemSpan.FullLine) { - EmptyState(noteType = success.noteType) + EmptyState(mainData = success.mainData) } } if (pinNotePad.first.isNotEmpty()) { diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt index d6f078fd..0a97051d 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainState.kt @@ -2,24 +2,21 @@ package com.mshdabiola.main import com.mshdabiola.model.MainData import com.mshdabiola.model.NotePad -import com.mshdabiola.model.NoteType sealed class MainState { data object Loading : MainState() data class Success( - val isSearch: Boolean = false, - val noteType: NoteType = NoteType.NOTE, +// val isSearch: Boolean = false, +// val noteType: NoteType = NoteType.NOTE, val notePads: List = emptyList(), - val types: List = emptyList(), - val color: List = emptyList(), - val label: List = emptyList(), - val searchSort: SearchSort? = null, - val mainData: MainData = MainData.Note, +// val types: List = emptyList(), +// val color: List = emptyList(), +// val label: List = emptyList(), +// val searchSort: SearchSort? = null, + val mainData: MainData = MainData(), val setOfSelected: Set = emptySet(), ) : MainState() // data class Error(val message: String) : MainStateN() - data object Empty : MainState() - data class Finish(val id: Long) : MainState() } diff --git a/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt b/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt index b2e45382..4dcb4343 100644 --- a/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt +++ b/feature/main/src/main/kotlin/com/mshdabiola/main/MainViewModel.kt @@ -1,18 +1,15 @@ package com.mshdabiola.main import android.util.Log -import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.DatePickerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TimePickerState -import androidx.compose.runtime.snapshotFlow import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.mshdabiola.common.IAlarmManager import com.mshdabiola.data.repository.INotePadRepository import com.mshdabiola.data.repository.UserDataRepository -import com.mshdabiola.model.MainData import com.mshdabiola.model.NoteType import com.mshdabiola.ui.state.DateDialogUiData import com.mshdabiola.ui.state.DateListUiState @@ -21,13 +18,13 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.datetime.Clock @@ -55,138 +52,66 @@ internal class MainViewModel userDataRepository: UserDataRepository, ) : ViewModel() { - val searchState = TextFieldState() + private val _dateTimeState = MutableStateFlow(DateDialogUiData()) + val dateTimeState = _dateTimeState.asStateFlow() + private lateinit var currentDateTime: LocalDateTime + private lateinit var today: LocalDateTime + private val timeListDefault = mutableListOf( + LocalTime(7, 0, 0), + LocalTime(13, 0, 0), + LocalTime(19, 0, 0), + LocalTime(20, 0, 0), + LocalTime(20, 0, 0), + ) - private val _mainState = MutableStateFlow(MainState.Loading) - val mainState = _mainState.asStateFlow() + @OptIn(ExperimentalMaterial3Api::class) + var datePicker: DatePickerState = DatePickerState( + initialSelectedDateMillis = System.currentTimeMillis(), + locale = Locale.getDefault(), + ) - init { - viewModelScope.launch { - userDataRepository.userData.collectLatest { - if (mainState.value is MainState.Success) { - _mainState.value = getSuccess().copy(mainData = it.mainData) - } - } - } + @OptIn(ExperimentalMaterial3Api::class) + var timePicker: TimePickerState = TimePickerState(12, 4, is24Hour = false) + private lateinit var currentLocalDate: LocalDate - viewModelScope.launch { - combine( - mainState, - snapshotFlow { searchState.text } - .debounce(500), - notepadRepository.getNotePads(), - ) { mainState, search, notepad -> - Triple(mainState, search, notepad) - }.collectLatest { triple -> - - val (mainState, search, notepad) = triple - if (mainState is MainState.Success) { - if (mainState.isSearch) { - when { - mainState.searchSort != null -> { - var list = when (val searchsort = mainState.searchSort) { - is SearchSort.Color -> { - notepad.filter { it.color == searchsort.colorIndex } - } - is SearchSort.Label -> { - notepad.filter { it.labels.any { it.id == searchsort.id } } - } - is SearchSort.Type -> { - when (searchsort.index) { - 0 -> notepad.filter { it.reminder > 0 } - 1 -> notepad.filter { it.isCheck } - 2 -> notepad.filter { it.images.isNotEmpty() } - 3 -> notepad.filter { it.voices.isNotEmpty() } - 4 -> notepad.filter { it.images.any { it.isDrawing } } - 5 -> notepad.filter { it.uris.isNotEmpty() } - else -> notepad - } - } - - null -> TODO() - } - - if (search.isNotBlank()) { - list = list.filter { it.toString().contains(search, true) } - } - - _mainState.update { - getSuccess().copy(notePads = list) - } - } - search.isNotBlank() -> { - - val list = notepad.filter { it.toString().contains(search, true) } - _mainState.update { - getSuccess().copy(notePads = list) - } - } - - else -> { - _mainState.value = getSuccess().copy( - notePads = emptyList(), - ) - } - } - } else { - - val list = - when (mainState.mainData) { - is MainData.Label -> { - notepad.filter { it.labels.any { it.id == mainState.mainData.index } } - } - - is MainData.Remainder -> { - notepad.filter { it.reminder > 0 } - } - - else -> { - notepad.filter { - it.noteType.index == mainState.mainData.index - } - } - } - _mainState.value = getSuccess().copy( - notePads = list, - ) - } - } else { - val mainData = userDataRepository.userData.mapLatest { it.mainData }.firstOrNull() ?: MainData.Note - _mainState.value = MainState.Success( - notePads = emptyList(), - mainData = mainData, - ) - - initDate() - } - } + private val setOfSelected = MutableStateFlow>(setOf()) + private val currentNotepads = userDataRepository + .userData + .mapLatest { it.mainData } + .flatMapLatest { + notepadRepository.getNotePadsWithMainData(it) } - } + val mainState = combine( + currentNotepads, + userDataRepository.userData.mapLatest { it.mainData }, + setOfSelected, + ) { notepad, mainData, setOfSelected -> + + MainState.Success( + notePads = notepad, + mainData = mainData, + setOfSelected = setOfSelected, + ) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = MainState.Loading, + ) - /** - * Handles the selection/deselection of a notepad card. - * - * This function is triggered when a user selects or deselects a notepad card. - * It updates the selected state of the corresponding notepad in the list and - * updates the UI state accordingly. - * - * @param id The ID of the notepad card that was selected or deselected. - */ fun onSelectCard(id: Long) { val selected = getSuccess().setOfSelected if (selected.contains(id)) { - _mainState.value = getSuccess().copy(setOfSelected = selected - id) + setOfSelected.value = selected - id } else { - _mainState.value = getSuccess().copy(setOfSelected = selected + id) + setOfSelected.value = selected + id } } fun clearSelected() { - _mainState.value = getSuccess().copy(setOfSelected = emptySet()) + setOfSelected.value = emptySet() } fun setNoteType(noteType: NoteType) { - _mainState.value = MainState.Success(noteType = noteType) } fun setPin() { @@ -344,28 +269,6 @@ internal class MainViewModel } } - private val _dateTimeState = MutableStateFlow(DateDialogUiData()) - val dateTimeState = _dateTimeState.asStateFlow() - private lateinit var currentDateTime: LocalDateTime - private lateinit var today: LocalDateTime - private val timeListDefault = mutableListOf( - LocalTime(7, 0, 0), - LocalTime(13, 0, 0), - LocalTime(19, 0, 0), - LocalTime(20, 0, 0), - LocalTime(20, 0, 0), - ) - - @OptIn(ExperimentalMaterial3Api::class) - var datePicker: DatePickerState = DatePickerState( - initialSelectedDateMillis = System.currentTimeMillis(), - locale = Locale.getDefault(), - ) - - @OptIn(ExperimentalMaterial3Api::class) - var timePicker: TimePickerState = TimePickerState(12, 4, is24Hour = false) - private lateinit var currentLocalDate: LocalDate - // date and time dialog logic private fun initDate() { @@ -656,75 +559,4 @@ internal class MainViewModel } private fun getSuccess() = mainState.value as MainState.Success - - fun toggleSearch() { - viewModelScope.launch { - val isSearch = getSuccess().isSearch - - if (isSearch) { - _mainState.update { - getSuccess().copy( - isSearch = false, - types = emptyList(), - color = emptyList(), - label = emptyList(), - searchSort = null, - ) - } - } else { - val notes = notepadRepository.getNotePads().first() - - val labels = notes.asSequence().filter { it.labels.isEmpty().not() } - .map { it.labels } - .flatten() - .distinct() - .map { SearchSort.Label(it.label, 6, it.id) }.toList() - - val colors = notes.asSequence() - .map { it.color } - .distinct() - .map { SearchSort.Color(it) }.toList() - - val type = ArrayList(6) - if (notes.any { it.reminder > 0 }) { - type.add(SearchSort.Type(0)) - } - if (notes.any { it.isCheck }) { - type.add(SearchSort.Type(1)) - } - if (notes.any { it.images.isNotEmpty() }) { - type.add(SearchSort.Type(2)) - } - if (notes.any { it.voices.isNotEmpty() }) { - type.add(SearchSort.Type(3)) - } - - if (notes.any { it.images.any { it.isDrawing } }) { - type.add(SearchSort.Type(4)) - } - - if (notes.any { it.uris.isNotEmpty() }) { - type.add(SearchSort.Type(5)) - } - - _mainState.update { - getSuccess().copy( - isSearch = true, - searchSort = null, - types = type, - color = colors, - label = labels, - ) - } - } - } - } - - fun onSetSearch(searchSort: SearchSort?) { - _mainState.update { - getSuccess().copy( - searchSort = searchSort, - ) - } - } } From 34473d03b54be81472526cc5c9d02c089f5d2f43 Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:30:56 +0100 Subject: [PATCH 033/790] Refactor: Update `setMainData` call in `LabelViewModel` This commit updates the `LabelViewModel.kt` to call `userDataRepository.setMainData(MainData())` instead of `userDataRepository.setMainData(MainData.Note.index)` when a label is deleted. - In `feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt`: - Changed the argument passed to `userDataRepository.setMainData` from `MainData.Note.index` to `MainData()` within the `deleteLabel` function. --- .../src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt b/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt index 106a7137..a1d254a1 100644 --- a/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt +++ b/feature/labelscreen/src/main/java/com/mshdabiola/labelscreen/LabelViewModel.kt @@ -61,7 +61,7 @@ class LabelViewModel @Inject constructor( labels.removeAt(index) labelScreenUiState = labelScreenUiState.copy(labels = labels.toImmutableList()) viewModelScope.launch { - userDataRepository.setMainData(MainData.Note.index) + userDataRepository.setMainData(MainData()) labelRepository.delete(id) } } From c521ce9f9058df9ed05d538ac930500e8b89048b Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Fri, 13 Jun 2025 23:31:16 +0100 Subject: [PATCH 034/790] Refactor: Use `MainData` object for main navigation argument This commit updates the main navigation logic to use a `MainData` object instead of a `Long` to represent the current navigation argument. This improves type safety and clarity. **Key changes:** - **`SharedActivityViewModel.kt`**: - Updated the default `mainData` in `UiState` to be a `MainData()` object. - **`MainActivityViewModel.kt`**: - Modified `setMainData` function to accept a `MainData` object. - **`MainNavigatin.kt`**: - Changed the type of `currentMainArg` from `Long` to `MainData`. - Updated `onNavigation` lambda to accept a `MainData` object. - Modified selection logic for `NavigationDrawerItem`s to compare with `currentMainArg.noteType` or `currentMainArg.index` based on the item type. - Updated `onClick` handlers to pass `MainData` objects. - **`NoteApp.kt`**: - Updated the `currentMainArg` in `MainNavigation` to use `userData.mainData` directly, providing a `MainData()` default. --- .../playnotepad/MainActivityViewModel.kt | 3 ++- .../playnotepad/SharedActivityViewModel.kt | 2 +- .../playnotepad/ui/MainNavigatin.kt | 25 ++++++++++--------- .../com/mshdabiola/playnotepad/ui/NoteApp.kt | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/mshdabiola/playnotepad/MainActivityViewModel.kt b/app/src/main/java/com/mshdabiola/playnotepad/MainActivityViewModel.kt index 9254b7ef..c1c906f4 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/MainActivityViewModel.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/MainActivityViewModel.kt @@ -9,6 +9,7 @@ import androidx.lifecycle.viewModelScope import com.mshdabiola.data.repository.ILabelRepository import com.mshdabiola.data.repository.INotePadRepository import com.mshdabiola.data.repository.UserDataRepository +import com.mshdabiola.model.MainData import com.mshdabiola.model.NoteCheck import com.mshdabiola.model.NoteImage import com.mshdabiola.model.NotePad @@ -115,7 +116,7 @@ class MainActivityViewModel @Inject constructor( return notePadRepository.upsert(notePad) } - fun setMainData(mainData: Long) { + fun setMainData(mainData: MainData) { viewModelScope.launch { userDataRepository.setMainData(mainData) } diff --git a/app/src/main/java/com/mshdabiola/playnotepad/SharedActivityViewModel.kt b/app/src/main/java/com/mshdabiola/playnotepad/SharedActivityViewModel.kt index 50667fb0..f1b9475e 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/SharedActivityViewModel.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/SharedActivityViewModel.kt @@ -157,7 +157,7 @@ sealed interface SharedActivityUiState { useDynamicColor = false, shouldHideOnboarding = false, contrast = Contrast.High, - mainData = MainData.Note, + mainData = MainData(), ), val notepad: NotePad = NotePad(), val labels: List