├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── akshayashokcode │ │ └── notepad │ │ ├── ExampleInstrumentedTest.kt │ │ ├── HiltTestRunner.kt │ │ ├── di │ │ └── TestAppModule.kt │ │ └── feature_note │ │ └── presentation │ │ ├── NotesEndToEndTest.kt │ │ └── notes │ │ └── NotesScreenTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── akshayashokcode │ │ │ └── notepad │ │ │ ├── NoteApp.kt │ │ │ ├── core │ │ │ └── util │ │ │ │ └── TestTags.kt │ │ │ ├── di │ │ │ └── AppModule.kt │ │ │ ├── feature_note │ │ │ ├── data │ │ │ │ ├── data_source │ │ │ │ │ ├── NoteDao.kt │ │ │ │ │ └── NoteDataBase.kt │ │ │ │ └── repository │ │ │ │ │ └── NoteRepositoryImpl.kt │ │ │ ├── domain │ │ │ │ ├── model │ │ │ │ │ └── Note.kt │ │ │ │ ├── repository │ │ │ │ │ └── NoteRepository.kt │ │ │ │ ├── use_case │ │ │ │ │ ├── AddNote.kt │ │ │ │ │ ├── DeleteNote.kt │ │ │ │ │ ├── GetNote.kt │ │ │ │ │ ├── GetNotes.kt │ │ │ │ │ └── NoteUseCases.kt │ │ │ │ └── util │ │ │ │ │ ├── NoteOrder.kt │ │ │ │ │ └── OrderType.kt │ │ │ └── presentation │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── add_edit_note │ │ │ │ ├── AddEditNoteEvent.kt │ │ │ │ ├── AddEditNoteScreen.kt │ │ │ │ ├── AddEditNoteViewModel.kt │ │ │ │ ├── NoteTextFieldState.kt │ │ │ │ └── components │ │ │ │ │ ├── AppKeyboardFocusManager.kt │ │ │ │ │ └── TransparentHintTextField.kt │ │ │ │ ├── notes │ │ │ │ ├── NotesEvent.kt │ │ │ │ ├── NotesScreen.kt │ │ │ │ ├── NotesState.kt │ │ │ │ ├── NotesViewModel.kt │ │ │ │ └── components │ │ │ │ │ ├── DefaultRadioButton.kt │ │ │ │ │ ├── EmptyScreenText.kt │ │ │ │ │ ├── NoteItem.kt │ │ │ │ │ └── OrderSection.kt │ │ │ │ └── util │ │ │ │ ├── KeyBoardManager.kt │ │ │ │ └── Screen.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── notepad_icon.png │ │ ├── font │ │ └── varela_round_regular.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── akshayashokcode │ └── notepad │ ├── ExampleUnitTest.kt │ └── feature_note │ ├── data │ └── repository │ │ └── FakeNoteRepository.kt │ └── domain │ └── use_case │ ├── AddNoteTest.kt │ └── GetNotesTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png └── screenshot5.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NotePad 2 | A simple notepad Allows the users to create & edit notes with different color themes to categorize them. 3 | 4 | Users can sort notes by title, date, or color in both ascending & descending order. 5 | 6 | Built with Kotlin using Room DB, Coroutines, Dagger Hilt, LiveData, Canvas, and following Model-View-ViewModel (MVVM) with clean architecture & it’s designed using jetpack compose. 7 |
8 |
9 | ## Features : 10 | - **Create Notes** 11 | - **Delete Notes** 12 | - **Select Note Colours to group notes** 13 | - **Re-edit Notes** 14 | - **Sort Notes by title, date or color** 15 |
16 | 17 | ## ScreenShots : 18 |      19 |      20 | 21 |      22 |      23 | 24 | *You can Install the latest NotePad app from here 👉* [](https://play.google.com/store/apps/details?id=com.akshayashokcode.notepad) 25 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | } 7 | 8 | android { 9 | compileSdk 34 10 | 11 | defaultConfig { 12 | applicationId "com.akshayashokcode.notepad" 13 | minSdk 23 14 | targetSdk 34 15 | versionCode 10 16 | versionName "1.3.0" 17 | 18 | testInstrumentationRunner "com.akshayashokcode.notepad.HiltTestRunner" 19 | vectorDrawables { 20 | useSupportLibrary true 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | minifyEnabled true 27 | shrinkResources true 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | //useIR = true 38 | } 39 | buildFeatures { 40 | compose true 41 | } 42 | composeOptions { 43 | kotlinCompilerExtensionVersion compose_version 44 | kotlinCompilerVersion '1.5.21' 45 | } 46 | packagingOptions { 47 | resources { 48 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 49 | } 50 | } 51 | namespace 'com.akshayashokcode.notepad' 52 | } 53 | 54 | dependencies { 55 | 56 | implementation 'androidx.core:core-ktx:1.12.0' 57 | implementation 'androidx.appcompat:appcompat:1.6.1' 58 | implementation 'com.google.android.material:material:1.10.0' 59 | implementation "androidx.compose.ui:ui:$compose_version" 60 | implementation "androidx.compose.material:material:$compose_version" 61 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 62 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' 63 | implementation 'androidx.activity:activity-compose:1.8.1' 64 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 65 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 66 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 67 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 68 | 69 | // Compose dependencies 70 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2" 71 | implementation "androidx.navigation:navigation-compose:2.7.5" 72 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 73 | implementation "androidx.hilt:hilt-navigation-compose:1.1.0" 74 | 75 | // Coroutines 76 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1' 77 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' 78 | 79 | //Dagger - Hilt 80 | implementation "com.google.dagger:hilt-android:2.49" 81 | kapt "com.google.dagger:hilt-android-compiler:2.49" 82 | // implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" 83 | kapt "androidx.hilt:hilt-compiler:1.1.0" 84 | 85 | // Room 86 | implementation "androidx.room:room-runtime:2.6.1" 87 | kapt "androidx.room:room-compiler:2.6.1" 88 | 89 | // Kotlin Extensions and Coroutines support for Room 90 | implementation "androidx.room:room-ktx:2.6.1" 91 | 92 | //set theme 93 | implementation "com.google.accompanist:accompanist-systemuicontroller:0.17.0" 94 | 95 | //in-App update 96 | // implementation 'com.google.android.play:core:1.10.3' 97 | implementation 'com.google.android.play:app-update:2.1.0' 98 | 99 | 100 | // Local unit tests 101 | testImplementation "androidx.test:core:1.5.0" 102 | testImplementation "junit:junit:4.13.2" 103 | testImplementation "androidx.arch.core:core-testing:2.2.0" 104 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4" 105 | testImplementation "com.google.truth:truth:1.1.3" 106 | testImplementation "com.squareup.okhttp3:mockwebserver:4.9.1" 107 | testImplementation "io.mockk:mockk:1.10.5" 108 | debugImplementation "androidx.compose.ui:ui-test-manifest:1.5.4" 109 | 110 | // Instrumentation tests 111 | androidTestImplementation 'com.google.dagger:hilt-android-testing:2.37' 112 | kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.37' 113 | androidTestImplementation "junit:junit:4.13.2" 114 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4" 115 | androidTestImplementation "androidx.arch.core:core-testing:2.2.0" 116 | androidTestImplementation "com.google.truth:truth:1.1.3" 117 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 118 | androidTestImplementation 'androidx.test:core-ktx:1.5.0' 119 | androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.9.1" 120 | androidTestImplementation "io.mockk:mockk-android:1.10.5" 121 | androidTestImplementation 'androidx.test:runner:1.5.2' 122 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akshayashokcode/notepad/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.akshayashokcode.notepad", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akshayashokcode/notepad/HiltTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | class HiltTestRunner : AndroidJUnitRunner() { 9 | 10 | override fun newApplication( 11 | cl: ClassLoader?, 12 | className: String?, 13 | context: Context? 14 | ): Application { 15 | return super.newApplication(cl, HiltTestApplication::class.java.name, context) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akshayashokcode/notepad/di/TestAppModule.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.di 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.akshayashokcode.notepad.feature_note.data.data_source.NoteDataBase 6 | import com.akshayashokcode.notepad.feature_note.data.repository.NoteRepositoryImpl 7 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 8 | import com.akshayashokcode.notepad.feature_note.domain.use_case.* 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object TestAppModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideNoteDatabase(app:Application):NoteDataBase{ 22 | return Room.inMemoryDatabaseBuilder( 23 | app, 24 | NoteDataBase::class.java 25 | ).build() 26 | } 27 | 28 | @Provides 29 | @Singleton 30 | fun provideNoteRepository(db:NoteDataBase):NoteRepository{ 31 | return NoteRepositoryImpl(db.noteDao) 32 | } 33 | 34 | @Provides 35 | @Singleton 36 | fun provideNoteUseCases(repository: NoteRepository):NoteUseCases{ 37 | return NoteUseCases( 38 | getNotes = GetNotes(repository), 39 | deleteNote = DeleteNote(repository), 40 | addNote = AddNote(repository), 41 | getNote = GetNote(repository) 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akshayashokcode/notepad/feature_note/presentation/NotesEndToEndTest.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation 2 | 3 | import androidx.compose.animation.ExperimentalAnimationApi 4 | import androidx.compose.ui.test.* 5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 6 | import androidx.navigation.NavType 7 | import androidx.navigation.compose.NavHost 8 | import androidx.navigation.compose.composable 9 | import androidx.navigation.compose.rememberNavController 10 | import androidx.navigation.navArgument 11 | import com.akshayashokcode.notepad.core.util.TestTags 12 | import com.akshayashokcode.notepad.di.AppModule 13 | import com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.AddEditNoteScreen 14 | import com.akshayashokcode.notepad.feature_note.presentation.notes.NotesScreen 15 | import com.akshayashokcode.notepad.feature_note.presentation.util.Screen 16 | import com.akshayashokcode.notepad.ui.theme.NotePadTheme 17 | import dagger.hilt.android.testing.HiltAndroidRule 18 | import dagger.hilt.android.testing.HiltAndroidTest 19 | import dagger.hilt.android.testing.UninstallModules 20 | import org.junit.Before 21 | import org.junit.Rule 22 | import org.junit.Test 23 | 24 | @HiltAndroidTest 25 | @UninstallModules(AppModule::class) 26 | class NotesEndToEndTest { 27 | 28 | @get:Rule(order = 0) 29 | val hiltRule = HiltAndroidRule(this) 30 | 31 | @get:Rule(order = 1) 32 | val composeRule = createAndroidComposeRule() 33 | 34 | @ExperimentalAnimationApi 35 | @Before 36 | fun setUp() { 37 | hiltRule.inject() 38 | composeRule.setContent { 39 | NotePadTheme() { 40 | val navController = rememberNavController() 41 | NavHost( 42 | navController = navController, 43 | startDestination = Screen.NotesScreen.route 44 | ) { 45 | composable(route = Screen.NotesScreen.route) { 46 | NotesScreen(navController = navController) 47 | } 48 | composable( 49 | route = Screen.AddEditNoteScreen.route + 50 | "?noteId={noteId}¬eColor={noteColor}", 51 | arguments = listOf( 52 | navArgument( 53 | name = "noteId" 54 | ) { 55 | type = NavType.IntType 56 | defaultValue = -1 57 | }, 58 | navArgument( 59 | name = "noteColor" 60 | ) { 61 | type = NavType.IntType 62 | defaultValue = -1 63 | }, 64 | ) 65 | ) { 66 | val color = it.arguments?.getInt("noteColor") ?: -1 67 | AddEditNoteScreen( 68 | navController = navController, 69 | noteColor = color 70 | ) 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | @Test 78 | fun saveNewNote_editAfterwards() { 79 | // Click on FAB to get to add note screen 80 | composeRule.onNodeWithContentDescription("Add").performClick() 81 | 82 | // Enter texts in title and content text fields 83 | composeRule 84 | .onNodeWithTag(TestTags.TITLE_TEXT_FIELD) 85 | .performTextInput("test-title") 86 | composeRule 87 | .onNodeWithTag(TestTags.CONTENT_TEXT_FIELD) 88 | .performTextInput("test-content") 89 | // Save the new 90 | composeRule.onNodeWithContentDescription("Save").performClick() 91 | 92 | // Make sure there is a note in the list with our title and content 93 | composeRule.onNodeWithText("test-title").assertIsDisplayed() 94 | // Click on note to edit it 95 | composeRule.onNodeWithText("test-title").performClick() 96 | 97 | // Make sure title and content text fields contain note title and content 98 | composeRule 99 | .onNodeWithTag(TestTags.TITLE_TEXT_FIELD) 100 | .assertTextEquals("test-title") 101 | composeRule 102 | .onNodeWithTag(TestTags.CONTENT_TEXT_FIELD) 103 | .assertTextEquals("test-content") 104 | // Add the text "2" to the title text field 105 | composeRule 106 | .onNodeWithTag(TestTags.TITLE_TEXT_FIELD) 107 | .performTextInput("2") 108 | // Update the note 109 | composeRule.onNodeWithContentDescription("Save").performClick() 110 | 111 | // Make sure the update was applied to the list 112 | composeRule.onNodeWithText("test-title2").assertIsDisplayed() 113 | } 114 | 115 | @Test 116 | fun saveNewNotes_orderByTitleDescending() { 117 | for(i in 1..3) { 118 | // Click on FAB to get to add note screen 119 | composeRule.onNodeWithContentDescription("Add").performClick() 120 | 121 | // Enter texts in title and content text fields 122 | composeRule 123 | .onNodeWithTag(TestTags.TITLE_TEXT_FIELD) 124 | .performTextInput(i.toString()) 125 | composeRule 126 | .onNodeWithTag(TestTags.CONTENT_TEXT_FIELD) 127 | .performTextInput(i.toString()) 128 | // Save the new 129 | composeRule.onNodeWithContentDescription("Save").performClick() 130 | } 131 | 132 | composeRule.onNodeWithText("1").assertIsDisplayed() 133 | composeRule.onNodeWithText("2").assertIsDisplayed() 134 | composeRule.onNodeWithText("3").assertIsDisplayed() 135 | 136 | composeRule 137 | .onNodeWithContentDescription("Sort") 138 | .performClick() 139 | composeRule 140 | .onNodeWithContentDescription("Title") 141 | .performClick() 142 | composeRule 143 | .onNodeWithContentDescription("Descending") 144 | .performClick() 145 | 146 | composeRule.onAllNodesWithTag(TestTags.NOTE_ITEM)[0] 147 | .assertTextContains("3") 148 | composeRule.onAllNodesWithTag(TestTags.NOTE_ITEM)[1] 149 | .assertTextContains("2") 150 | composeRule.onAllNodesWithTag(TestTags.NOTE_ITEM)[2] 151 | .assertTextContains("1") 152 | } 153 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/akshayashokcode/notepad/feature_note/presentation/notes/NotesScreenTest.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes 2 | 3 | import androidx.compose.animation.ExperimentalAnimationApi 4 | import androidx.compose.ui.test.* 5 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 6 | import androidx.navigation.compose.NavHost 7 | import androidx.navigation.compose.composable 8 | import androidx.navigation.compose.rememberNavController 9 | import com.akshayashokcode.notepad.core.util.TestTags 10 | import com.akshayashokcode.notepad.di.AppModule 11 | import com.akshayashokcode.notepad.feature_note.presentation.MainActivity 12 | import com.akshayashokcode.notepad.feature_note.presentation.util.Screen 13 | import com.akshayashokcode.notepad.ui.theme.NotePadTheme 14 | import dagger.hilt.android.testing.HiltAndroidRule 15 | import dagger.hilt.android.testing.HiltAndroidTest 16 | import dagger.hilt.android.testing.UninstallModules 17 | import org.junit.Before 18 | import org.junit.Rule 19 | import org.junit.Test 20 | 21 | @HiltAndroidTest 22 | @UninstallModules(AppModule::class) 23 | class NotesScreenTest { 24 | 25 | @get:Rule(order = 0) 26 | val hiltRule = HiltAndroidRule(this) 27 | 28 | @get:Rule(order = 1) 29 | val composeRule = createAndroidComposeRule() 30 | 31 | @ExperimentalAnimationApi 32 | @Before 33 | fun setUp() { 34 | hiltRule.inject() 35 | composeRule.setContent { 36 | val navController = rememberNavController() 37 | NotePadTheme { 38 | NavHost( 39 | navController = navController, 40 | startDestination = Screen.NotesScreen.route 41 | ) { 42 | composable(route = Screen.NotesScreen.route) { 43 | NotesScreen(navController = navController) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | @Test 51 | fun clickToggleOrderSection_isVisible() { 52 | for(i in 1..3) { 53 | // Click on FAB to get to add note screen 54 | composeRule.onNodeWithContentDescription("Add").performClick() 55 | 56 | // Enter texts in title and content text fields 57 | composeRule 58 | .onNodeWithTag(TestTags.TITLE_TEXT_FIELD) 59 | .performTextInput(i.toString()) 60 | composeRule 61 | .onNodeWithTag(TestTags.CONTENT_TEXT_FIELD) 62 | .performTextInput(i.toString()) 63 | // Save the new 64 | composeRule.onNodeWithContentDescription("Save").performClick() 65 | } 66 | composeRule.onNodeWithTag(TestTags.ORDER_SECTION).assertDoesNotExist() 67 | composeRule.onNodeWithContentDescription("Sort").performClick() 68 | composeRule.onNodeWithTag(TestTags.ORDER_SECTION).assertIsDisplayed() 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/NoteApp.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class NoteApp:Application() -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/core/util/TestTags.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.core.util 2 | 3 | object TestTags { 4 | const val ORDER_SECTION = "ORDER_SECTION" 5 | const val TITLE_TEXT_FIELD = "TITLE_TEXT_FIELD" 6 | const val CONTENT_TEXT_FIELD = "CONTENT_TEXT_FIELD" 7 | const val NOTE_ITEM = "NOTE_ITEM" 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.di 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.akshayashokcode.notepad.feature_note.data.data_source.NoteDataBase 6 | import com.akshayashokcode.notepad.feature_note.data.repository.NoteRepositoryImpl 7 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 8 | import com.akshayashokcode.notepad.feature_note.domain.use_case.* 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object AppModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideNoteDatabase(app:Application):NoteDataBase{ 22 | return Room.databaseBuilder( 23 | app, 24 | NoteDataBase::class.java, 25 | NoteDataBase.DATABASE_NAME 26 | ).build() 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | fun provideNoteRepository(db:NoteDataBase):NoteRepository{ 32 | return NoteRepositoryImpl(db.noteDao) 33 | } 34 | 35 | @Provides 36 | @Singleton 37 | fun provideNoteUseCases(repository: NoteRepository):NoteUseCases{ 38 | return NoteUseCases( 39 | getNotes = GetNotes(repository), 40 | deleteNote = DeleteNote(repository), 41 | addNote = AddNote(repository), 42 | getNote = GetNote(repository) 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/data/data_source/NoteDao.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.data.data_source 2 | 3 | import androidx.room.* 4 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | @Dao 8 | interface NoteDao { 9 | 10 | @Query("SELECT * FROM note") 11 | fun getNotes(): Flow> 12 | 13 | @Query("SELECT * FROM note WHERE id= :id") 14 | suspend fun getNoteById(id: Int): Note? 15 | 16 | @Insert(onConflict = OnConflictStrategy.REPLACE) 17 | suspend fun insertNote(note: Note) 18 | 19 | @Delete 20 | suspend fun deleteNote(note: Note) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/data/data_source/NoteDataBase.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.data.data_source 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 6 | 7 | @Database(entities = [Note::class], version = 1) 8 | abstract class NoteDataBase : RoomDatabase() { 9 | 10 | abstract val noteDao:NoteDao 11 | companion object{ 12 | const val DATABASE_NAME="notes_db" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/data/repository/NoteRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.data.repository 2 | 3 | import com.akshayashokcode.notepad.feature_note.data.data_source.NoteDao 4 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 5 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class NoteRepositoryImpl( 9 | private val dao:NoteDao 10 | ): NoteRepository { 11 | override fun getNotes(): Flow> { 12 | return dao.getNotes() 13 | } 14 | 15 | override suspend fun getNoteById(id: Int): Note? { 16 | return dao.getNoteById(id) 17 | } 18 | 19 | override suspend fun insertNote(note: Note) { 20 | return dao.insertNote(note) 21 | } 22 | 23 | override suspend fun deleteNote(note: Note) { 24 | return dao.deleteNote(note) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/model/Note.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.akshayashokcode.notepad.ui.theme.* 6 | import java.lang.Exception 7 | 8 | @Entity 9 | data class Note( 10 | val title:String, 11 | val content:String, 12 | val timeStamp: Long, 13 | val color:Int, 14 | @PrimaryKey val id:Int?=null 15 | ){ 16 | companion object{ 17 | val noteColors=listOf(RedOrange, LightGreen, Violet, BabyBlue, RedPink) 18 | } 19 | 20 | class InvalidNoteException(message:String):Exception(message) 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/repository/NoteRepository.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.repository 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface NoteRepository { 7 | 8 | fun getNotes(): Flow> 9 | 10 | suspend fun getNoteById(id: Int): Note? 11 | 12 | suspend fun insertNote(note: Note) 13 | 14 | suspend fun deleteNote(note: Note) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/use_case/AddNote.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.use_case 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 5 | import kotlin.jvm.Throws 6 | 7 | class AddNote( 8 | private val repository:NoteRepository 9 | ) { 10 | 11 | @Throws(Note.InvalidNoteException::class) 12 | suspend operator fun invoke(note: Note){ 13 | if(note.title.isBlank()){ 14 | throw Note.InvalidNoteException("The title of the note can't be empty.") 15 | } 16 | if(note.content.isBlank()){ 17 | throw Note.InvalidNoteException("The content of the note can't be empty.") 18 | } 19 | repository.insertNote(note) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/use_case/DeleteNote.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.use_case 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 5 | 6 | class DeleteNote( 7 | private val repository: NoteRepository 8 | ) { 9 | suspend operator fun invoke(note: Note){ 10 | repository.deleteNote(note) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/use_case/GetNote.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.use_case 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 5 | 6 | class GetNote( 7 | private val repository: NoteRepository 8 | ) { 9 | 10 | suspend operator fun invoke(id:Int): Note?{ 11 | return repository.getNoteById(id) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/use_case/GetNotes.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.use_case 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import com.akshayashokcode.notepad.feature_note.domain.repository.NoteRepository 5 | import com.akshayashokcode.notepad.feature_note.domain.util.NoteOrder 6 | import com.akshayashokcode.notepad.feature_note.domain.util.OrderType 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.map 9 | 10 | class GetNotes( 11 | private val repository: NoteRepository 12 | ) { 13 | 14 | operator fun invoke( 15 | noteOrder: NoteOrder=NoteOrder.Date(OrderType.Descending) 16 | ):Flow>{ 17 | return repository.getNotes().map { notes-> 18 | when(noteOrder.orderType){ 19 | is OrderType.Ascending ->{ 20 | when(noteOrder){ 21 | is NoteOrder.Title-> notes.sortedBy { it.title.lowercase() } 22 | is NoteOrder.Date-> notes.sortedBy { it.timeStamp } 23 | is NoteOrder.Color-> notes.sortedBy { it.color } 24 | } 25 | } 26 | is OrderType.Descending->{ 27 | when(noteOrder){ 28 | is NoteOrder.Title-> notes.sortedByDescending { it.title.lowercase() } 29 | is NoteOrder.Date-> notes.sortedByDescending { it.timeStamp } 30 | is NoteOrder.Color-> notes.sortedByDescending{ it.color } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/use_case/NoteUseCases.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.use_case 2 | 3 | data class NoteUseCases( 4 | val getNotes: GetNotes, 5 | val deleteNote: DeleteNote, 6 | val addNote: AddNote, 7 | val getNote:GetNote 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/util/NoteOrder.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.util 2 | 3 | sealed class NoteOrder(val orderType: OrderType){ 4 | class Title(orderType: OrderType):NoteOrder(orderType) 5 | class Date(orderType: OrderType):NoteOrder(orderType) 6 | class Color(orderType: OrderType):NoteOrder(orderType) 7 | 8 | fun copy(orderType: OrderType):NoteOrder{ 9 | return when(this){ 10 | is Title->Title(orderType) 11 | is Date->Date(orderType) 12 | is Color->Color(orderType) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/domain/util/OrderType.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.domain.util 2 | 3 | sealed class OrderType{ 4 | data object Ascending:OrderType() 5 | data object Descending:OrderType() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation 2 | 3 | import android.content.Intent 4 | import android.content.IntentSender 5 | import android.os.Bundle 6 | import android.util.Log 7 | import androidx.activity.ComponentActivity 8 | import androidx.activity.compose.setContent 9 | import androidx.annotation.Nullable 10 | import androidx.compose.animation.ExperimentalAnimationApi 11 | import androidx.compose.material.MaterialTheme 12 | import androidx.compose.material.Surface 13 | import androidx.navigation.NavType 14 | import androidx.navigation.compose.NavHost 15 | import androidx.navigation.compose.composable 16 | import androidx.navigation.compose.rememberNavController 17 | import androidx.navigation.navArgument 18 | import com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.AddEditNoteScreen 19 | import com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.components.AppKeyboardFocusManager 20 | import com.akshayashokcode.notepad.feature_note.presentation.notes.NotesScreen 21 | import com.akshayashokcode.notepad.feature_note.presentation.util.Screen 22 | import com.akshayashokcode.notepad.ui.theme.NotePadTheme 23 | import com.google.android.material.snackbar.Snackbar 24 | import com.google.android.play.core.appupdate.AppUpdateManager 25 | import com.google.android.play.core.appupdate.AppUpdateManagerFactory 26 | import com.google.android.play.core.install.InstallStateUpdatedListener 27 | import com.google.android.play.core.install.model.AppUpdateType 28 | import com.google.android.play.core.install.model.InstallStatus 29 | import com.google.android.play.core.install.model.UpdateAvailability 30 | import dagger.hilt.android.AndroidEntryPoint 31 | 32 | 33 | @AndroidEntryPoint 34 | class MainActivity : ComponentActivity() { 35 | private val REQUEST_CODE = 11 36 | private lateinit var appUpdateManager: AppUpdateManager 37 | private val TAG = "MainActivity" 38 | 39 | override fun onStart() { 40 | super.onStart() 41 | checkUpdate() 42 | } 43 | override fun onResume() { 44 | super.onResume() 45 | if (appUpdateManager != null) { 46 | appUpdateManager 47 | .appUpdateInfo 48 | .addOnSuccessListener { appUpdateInfo -> 49 | // If the update is downloaded but not installed, 50 | // notify the user to complete the update. 51 | if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) { 52 | popupSnackbarForCompleteUpdate() 53 | }else{ 54 | Log.d(TAG,"State of update: ${appUpdateInfo.installStatus()}") 55 | } 56 | } 57 | } 58 | } 59 | @ExperimentalAnimationApi 60 | override fun onCreate(savedInstanceState: Bundle?) { 61 | super.onCreate(savedInstanceState) 62 | setContent { 63 | AppKeyboardFocusManager() 64 | NotePadTheme { 65 | Surface( 66 | color = MaterialTheme.colors.background 67 | ) 68 | { 69 | val navController = rememberNavController() 70 | NavHost( 71 | navController = navController, 72 | startDestination = Screen.NotesScreen.route 73 | ) { 74 | composable(route = Screen.NotesScreen.route) { 75 | NotesScreen(navController = navController) 76 | } 77 | composable(route = Screen.AddEditNoteScreen.route + 78 | "?noteId={noteId}¬eColor={noteColor}", 79 | arguments = listOf( 80 | navArgument( 81 | name = "noteId" 82 | ) { 83 | type = NavType.IntType 84 | defaultValue = -1 85 | }, 86 | navArgument( 87 | name = "noteColor" 88 | ) { 89 | type = NavType.IntType 90 | defaultValue = -1 91 | } 92 | ) 93 | 94 | ) { 95 | val color = it.arguments?.getInt("noteColor") ?: -1 96 | AddEditNoteScreen( 97 | navController = navController, 98 | noteColor = color 99 | ) 100 | } 101 | } 102 | } 103 | 104 | } 105 | } 106 | } 107 | 108 | private fun checkUpdate() { 109 | // Returns an intent object that you use to check for an update. 110 | appUpdateManager = AppUpdateManagerFactory.create(this) 111 | val appUpdateInfoTask = appUpdateManager.appUpdateInfo 112 | // Checks that the platform will allow the specified type of update. 113 | appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> 114 | if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE 115 | && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) 116 | ) { 117 | // Request the update. 118 | try { 119 | appUpdateManager.startUpdateFlowForResult( 120 | // Pass the intent that is returned by 'getAppUpdateInfo()'. 121 | appUpdateInfo, 122 | // Or 'AppUpdateType.FLEXIBLE' for flexible updates. 123 | AppUpdateType.FLEXIBLE, 124 | // The current activity making the update request. 125 | this, 126 | // Include a request code to later monitor this update request. 127 | REQUEST_CODE 128 | ) 129 | } catch (e: IntentSender.SendIntentException) { 130 | e.printStackTrace() 131 | } 132 | } else { 133 | Log.d(TAG, "Update Status Not available:${appUpdateInfo.updateAvailability()}") 134 | } 135 | } 136 | appUpdateManager.registerListener(listener) 137 | } 138 | 139 | // Create a listener to track request state updates. 140 | private val listener = InstallStateUpdatedListener { state -> 141 | if (state.installStatus() == InstallStatus.DOWNLOADED) { 142 | // After the update is downloaded, show a notification 143 | // and request user confirmation to restart the app. 144 | popupSnackbarForCompleteUpdate() 145 | } 146 | // Log state or install the update. 147 | Log.d(TAG, "State of update: ${state.installStatus()}") 148 | } 149 | 150 | // Displays the snackbar notification and call to action. 151 | private fun popupSnackbarForCompleteUpdate() { 152 | Snackbar.make( 153 | findViewById(android.R.id.content), 154 | "An update has just been downloaded.", 155 | Snackbar.LENGTH_INDEFINITE 156 | ).apply { 157 | setAction("RESTART") { appUpdateManager.completeUpdate() } 158 | show() 159 | } 160 | } 161 | 162 | override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) { 163 | super.onActivityResult(requestCode, resultCode, data) 164 | if (requestCode == REQUEST_CODE) { 165 | Log.d(TAG, "onActivityResult: Updated to Latest Features") 166 | if (resultCode != RESULT_OK) { 167 | Log.e(TAG, "onActivityResult: app download failed") 168 | } 169 | } 170 | } 171 | 172 | override fun onStop() { 173 | if (appUpdateManager != null) { 174 | appUpdateManager.unregisterListener(listener) 175 | } 176 | super.onStop() 177 | } 178 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/add_edit_note/AddEditNoteEvent.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.add_edit_note 2 | 3 | import androidx.compose.ui.focus.FocusState 4 | 5 | sealed class AddEditNoteEvent{ 6 | data class EnteredTitle(val value:String):AddEditNoteEvent() 7 | data class ChangeTitleFocus(val focusState: FocusState):AddEditNoteEvent() 8 | data class EnteredContent(val value:String):AddEditNoteEvent() 9 | data class ChangeContentFocus(val focusState: FocusState):AddEditNoteEvent() 10 | data class ChangeColor(val color:Int):AddEditNoteEvent() 11 | data object SaveNote:AddEditNoteEvent() 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/add_edit_note/AddEditNoteScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.add_edit_note 2 | 3 | import androidx.compose.animation.Animatable 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.border 7 | import androidx.compose.foundation.clickable 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.foundation.shape.CircleShape 10 | import androidx.compose.material.* 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.filled.Save 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.LaunchedEffect 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.runtime.rememberCoroutineScope 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.draw.clip 19 | import androidx.compose.ui.draw.shadow 20 | import androidx.compose.ui.graphics.Color 21 | import androidx.compose.ui.graphics.toArgb 22 | import androidx.compose.ui.unit.dp 23 | import androidx.hilt.navigation.compose.hiltViewModel 24 | import androidx.navigation.NavController 25 | import com.akshayashokcode.notepad.core.util.TestTags 26 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 27 | import com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.components.TransparentHintTextFiled 28 | import com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.components.TransparentHintTextFiledContent 29 | import kotlinx.coroutines.flow.collectLatest 30 | import kotlinx.coroutines.launch 31 | 32 | @Composable 33 | fun AddEditNoteScreen( 34 | navController: NavController, 35 | noteColor: Int, 36 | viewModel: AddEditNoteViewModel = hiltViewModel() 37 | ) { 38 | val titleState = viewModel.noteTitle.value 39 | val contentState = viewModel.noteContent.value 40 | 41 | val scaffoldState = rememberScaffoldState() 42 | 43 | val noteBackgroundAnimatable = remember { 44 | Animatable( 45 | Color(if (noteColor != -1) noteColor else viewModel.noteColor.value) 46 | ) 47 | } 48 | val scope = rememberCoroutineScope() 49 | 50 | LaunchedEffect(key1 = true){ 51 | viewModel.eventFlow.collectLatest {event-> 52 | when(event){ 53 | is AddEditNoteViewModel.UiEvent.ShowSnackBar->{ 54 | scaffoldState.snackbarHostState.showSnackbar( 55 | message = event.message 56 | ) 57 | } 58 | is AddEditNoteViewModel.UiEvent.SaveNote->{ 59 | navController.navigateUp() 60 | } 61 | } 62 | } 63 | } 64 | 65 | Scaffold( 66 | floatingActionButton = { 67 | FloatingActionButton( 68 | onClick = { 69 | viewModel.onEvent(AddEditNoteEvent.SaveNote) 70 | }, 71 | backgroundColor = MaterialTheme.colors.primary 72 | ) { 73 | Icon(imageVector = Icons.Default.Save, contentDescription = "Save") 74 | } 75 | }, 76 | scaffoldState = scaffoldState 77 | ) { padding -> 78 | Column( 79 | modifier = Modifier 80 | .fillMaxSize() 81 | .background(noteBackgroundAnimatable.value) 82 | .padding(padding) 83 | .padding(16.dp) 84 | ) { 85 | Row( 86 | modifier = Modifier 87 | .fillMaxWidth() 88 | .padding(8.dp), 89 | horizontalArrangement = Arrangement.SpaceBetween 90 | ) { 91 | Note.noteColors.forEach { color -> 92 | val colorInt = color.toArgb() 93 | Box( 94 | modifier = Modifier 95 | .size(50.dp) 96 | .shadow(15.dp, CircleShape) 97 | .clip(CircleShape) 98 | .background(color) 99 | .border( 100 | width = 3.dp, 101 | color = if (viewModel.noteColor.value == colorInt) { 102 | Color.Black 103 | } else Color.Transparent, 104 | shape = CircleShape 105 | ) 106 | .clickable { 107 | scope.launch { 108 | noteBackgroundAnimatable.animateTo( 109 | targetValue = Color(colorInt), 110 | animationSpec = tween( 111 | durationMillis = 500 112 | ) 113 | ) 114 | } 115 | viewModel.onEvent(AddEditNoteEvent.ChangeColor(colorInt)) 116 | } 117 | ) 118 | } 119 | } 120 | Spacer(modifier = Modifier.height(16.dp)) 121 | 122 | TransparentHintTextFiled( 123 | text = titleState.text, 124 | hint =titleState.hint, 125 | onValueChange ={ 126 | viewModel.onEvent(AddEditNoteEvent.EnteredTitle(it)) 127 | }, 128 | onFocusChange ={ 129 | viewModel.onEvent(AddEditNoteEvent.ChangeTitleFocus(it)) 130 | }, 131 | isHintVisible = titleState.isHintVisible, 132 | singleLine = true, 133 | textStyle = MaterialTheme.typography.h5, 134 | testTag = TestTags.TITLE_TEXT_FIELD 135 | ) 136 | Spacer(modifier = Modifier.height(16.dp)) 137 | TransparentHintTextFiledContent( 138 | text = contentState.text, 139 | hint =contentState.hint, 140 | onValueChange ={ 141 | viewModel.onEvent(AddEditNoteEvent.EnteredContent(it)) 142 | }, 143 | onFocusChange ={ 144 | viewModel.onEvent(AddEditNoteEvent.ChangeContentFocus(it)) 145 | }, 146 | isHintVisible = contentState.isHintVisible, 147 | textStyle = MaterialTheme.typography.body1, 148 | modifier = Modifier.fillMaxHeight(), 149 | testTag = TestTags.CONTENT_TEXT_FIELD 150 | ) 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/add_edit_note/AddEditNoteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.add_edit_note 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.ui.graphics.toArgb 6 | import androidx.lifecycle.SavedStateHandle 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.viewModelScope 9 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 10 | import com.akshayashokcode.notepad.feature_note.domain.use_case.NoteUseCases 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.flow.MutableSharedFlow 13 | import kotlinx.coroutines.flow.asSharedFlow 14 | import kotlinx.coroutines.launch 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class AddEditNoteViewModel @Inject constructor( 19 | private val noteUseCases: NoteUseCases, 20 | savedStateHandle: SavedStateHandle 21 | ) : ViewModel() { 22 | 23 | private val _noteTitle = mutableStateOf( 24 | NoteTextFieldState( 25 | hint = "Enter title..." 26 | ) 27 | ) 28 | val noteTitle: State = _noteTitle 29 | 30 | private val _noteContent = mutableStateOf( 31 | NoteTextFieldState( 32 | hint = "Enter some content" 33 | ) 34 | ) 35 | val noteContent: State = _noteContent 36 | 37 | private val _noteColor = mutableStateOf(Note.noteColors.random().toArgb()) 38 | val noteColor: State = _noteColor 39 | 40 | private val _eventFlow = MutableSharedFlow() 41 | val eventFlow = _eventFlow.asSharedFlow() 42 | 43 | private var currentNoteId: Int? = null 44 | 45 | init { 46 | savedStateHandle.get("noteId")?.let { noteId -> 47 | if (noteId != -1) { 48 | viewModelScope.launch { 49 | noteUseCases.getNote(noteId)?.also { note -> 50 | currentNoteId=note.id 51 | _noteTitle.value=noteTitle.value.copy( 52 | text = note.title, 53 | isHintVisible = false 54 | ) 55 | //TODO 56 | _noteContent.value=noteContent.value.copy( 57 | text = note.content, 58 | isHintVisible = false 59 | ) 60 | _noteColor.value=note.color 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | fun onEvent(event: AddEditNoteEvent) { 68 | when (event) { 69 | is AddEditNoteEvent.EnteredTitle -> { 70 | _noteTitle.value = noteTitle.value.copy( 71 | text = event.value 72 | ) 73 | } 74 | is AddEditNoteEvent.ChangeTitleFocus -> { 75 | _noteTitle.value = noteTitle.value.copy( 76 | isHintVisible = !event.focusState.isFocused && 77 | noteTitle.value.text.isBlank() 78 | ) 79 | } 80 | //TODO 81 | is AddEditNoteEvent.EnteredContent -> { 82 | _noteContent.value = noteContent.value.copy( 83 | text = event.value 84 | ) 85 | } 86 | is AddEditNoteEvent.ChangeContentFocus -> { 87 | _noteContent.value = noteContent.value.copy( 88 | isHintVisible = !event.focusState.isFocused && 89 | noteContent.value.text.isBlank() 90 | ) 91 | } 92 | is AddEditNoteEvent.ChangeColor -> { 93 | _noteColor.value = event.color 94 | } 95 | is AddEditNoteEvent.SaveNote -> { 96 | viewModelScope.launch { 97 | try { 98 | noteUseCases.addNote( 99 | Note( 100 | title = noteTitle.value.text.trim(), 101 | content = noteContent.value.text.trim(), 102 | timeStamp = System.currentTimeMillis(), 103 | color = noteColor.value, 104 | id = currentNoteId 105 | ) 106 | ) 107 | _eventFlow.emit(UiEvent.SaveNote) 108 | } catch (e: Note.InvalidNoteException) { 109 | _eventFlow.emit( 110 | UiEvent.ShowSnackBar( 111 | message = e.message ?: "Couldn't save note" 112 | ) 113 | ) 114 | } 115 | } 116 | } 117 | 118 | } 119 | } 120 | 121 | sealed class UiEvent { 122 | data class ShowSnackBar(val message: String) : UiEvent() 123 | object SaveNote : UiEvent() 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/add_edit_note/NoteTextFieldState.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.add_edit_note 2 | 3 | data class NoteTextFieldState( 4 | val text:String="", 5 | val hint:String="", 6 | val isHintVisible:Boolean=true 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/add_edit_note/components/AppKeyboardFocusManager.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisposableEffect 5 | import androidx.compose.ui.platform.LocalContext 6 | import androidx.compose.ui.platform.LocalFocusManager 7 | import com.akshayashokcode.notepad.feature_note.presentation.util.KeyBoardManager 8 | 9 | @Composable 10 | fun AppKeyboardFocusManager() { 11 | val context = LocalContext.current 12 | val focusManager = LocalFocusManager.current 13 | DisposableEffect(key1 = context) { 14 | val keyboardManager = KeyBoardManager(context) 15 | keyboardManager.attachKeyboardDismissListener { 16 | focusManager.clearFocus() 17 | } 18 | onDispose { 19 | keyboardManager.release() 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/add_edit_note/components/TransparentHintTextField.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.add_edit_note.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.text.BasicTextField 7 | import androidx.compose.foundation.text.KeyboardOptions 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.focus.FocusState 12 | import androidx.compose.ui.focus.onFocusChanged 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.platform.testTag 15 | import androidx.compose.ui.text.TextStyle 16 | import androidx.compose.ui.text.input.KeyboardCapitalization 17 | 18 | 19 | @Composable 20 | fun TransparentHintTextFiled( 21 | text: String, 22 | hint: String, 23 | modifier: Modifier = Modifier, 24 | isHintVisible: Boolean = true, 25 | onValueChange: (String) -> Unit, 26 | textStyle: TextStyle = TextStyle(), 27 | singleLine: Boolean = false, 28 | testTag:String="", 29 | onFocusChange: (FocusState) -> Unit 30 | ) { 31 | Box(modifier = modifier) { 32 | BasicTextField( 33 | value = text, 34 | onValueChange = onValueChange, 35 | singleLine = singleLine, 36 | textStyle = textStyle, 37 | keyboardOptions = KeyboardOptions.Default.copy(capitalization = KeyboardCapitalization.Sentences), 38 | modifier = Modifier 39 | .testTag(testTag) 40 | .fillMaxWidth() 41 | .onFocusChanged { onFocusChange(it) } 42 | ) 43 | if (isHintVisible) { 44 | Text(text = hint, style = textStyle, color = Color.DarkGray) 45 | } 46 | } 47 | } 48 | 49 | @Composable 50 | fun TransparentHintTextFiledContent( 51 | text: String, 52 | hint: String, 53 | modifier: Modifier = Modifier, 54 | isHintVisible: Boolean = true, 55 | onValueChange: (String) -> Unit, 56 | textStyle: TextStyle = TextStyle(), 57 | singleLine: Boolean = false, 58 | testTag:String="", 59 | onFocusChange: (FocusState) -> Unit 60 | ) { 61 | Box(modifier = modifier) { 62 | BasicTextField( 63 | value = text, 64 | onValueChange = onValueChange, 65 | singleLine = singleLine, 66 | textStyle = textStyle, 67 | keyboardOptions = KeyboardOptions.Default.copy(capitalization = KeyboardCapitalization.Sentences), 68 | modifier = Modifier 69 | .testTag(testTag) 70 | .fillMaxSize() 71 | .onFocusChanged { onFocusChange(it) } 72 | ) 73 | if (isHintVisible) { 74 | Text(text = hint, style = textStyle, color = Color.DarkGray) 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/NotesEvent.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import com.akshayashokcode.notepad.feature_note.domain.util.NoteOrder 5 | 6 | sealed class NotesEvent{ 7 | data class Order(val noteOrder: NoteOrder):NotesEvent() 8 | data class DeleteNote(val note: Note):NotesEvent() 9 | data object RestoreNote:NotesEvent() 10 | data object ToggleOrderSection: NotesEvent() 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/NotesScreen.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.ExperimentalAnimationApi 5 | import androidx.compose.animation.core.LinearOutSlowInEasing 6 | import androidx.compose.animation.core.MutableTransitionState 7 | import androidx.compose.animation.core.tween 8 | import androidx.compose.animation.fadeIn 9 | import androidx.compose.animation.fadeOut 10 | import androidx.compose.animation.slideInVertically 11 | import androidx.compose.animation.slideOutVertically 12 | import androidx.compose.foundation.clickable 13 | import androidx.compose.foundation.layout.Arrangement 14 | import androidx.compose.foundation.layout.Column 15 | import androidx.compose.foundation.layout.Row 16 | import androidx.compose.foundation.layout.Spacer 17 | import androidx.compose.foundation.layout.fillMaxSize 18 | import androidx.compose.foundation.layout.fillMaxWidth 19 | import androidx.compose.foundation.layout.height 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.foundation.lazy.LazyColumn 22 | import androidx.compose.foundation.lazy.items 23 | import androidx.compose.material.FloatingActionButton 24 | import androidx.compose.material.Icon 25 | import androidx.compose.material.IconButton 26 | import androidx.compose.material.MaterialTheme 27 | import androidx.compose.material.Scaffold 28 | import androidx.compose.material.Snackbar 29 | import androidx.compose.material.SnackbarHost 30 | import androidx.compose.material.SnackbarResult 31 | import androidx.compose.material.Text 32 | import androidx.compose.material.icons.Icons 33 | import androidx.compose.material.icons.filled.Add 34 | import androidx.compose.material.icons.filled.Sort 35 | import androidx.compose.material.rememberScaffoldState 36 | import androidx.compose.runtime.Composable 37 | import androidx.compose.runtime.remember 38 | import androidx.compose.runtime.rememberCoroutineScope 39 | import androidx.compose.ui.Alignment 40 | import androidx.compose.ui.Modifier 41 | import androidx.compose.ui.platform.testTag 42 | import androidx.compose.ui.text.TextStyle 43 | import androidx.compose.ui.unit.dp 44 | import androidx.hilt.navigation.compose.hiltViewModel 45 | import androidx.navigation.NavController 46 | import com.akshayashokcode.notepad.core.util.TestTags 47 | import com.akshayashokcode.notepad.feature_note.presentation.notes.components.EmptyScreenText 48 | import com.akshayashokcode.notepad.feature_note.presentation.notes.components.NoteItem 49 | import com.akshayashokcode.notepad.feature_note.presentation.notes.components.OrderSection 50 | import com.akshayashokcode.notepad.feature_note.presentation.util.Screen 51 | import com.akshayashokcode.notepad.ui.theme.customTypography 52 | import kotlinx.coroutines.launch 53 | 54 | @ExperimentalAnimationApi 55 | @Composable 56 | fun NotesScreen( 57 | navController: NavController, 58 | viewModel: NotesViewModel = hiltViewModel() 59 | ) { 60 | val state = viewModel.state.value 61 | val scaffoldState = rememberScaffoldState() 62 | val scope = rememberCoroutineScope() 63 | val animVisibleState = remember { MutableTransitionState(false) } 64 | .apply { targetState = true } 65 | val notesAvailable = state.notes.isNotEmpty() 66 | Scaffold( 67 | floatingActionButton = { 68 | AnimatedVisibility( 69 | visibleState = animVisibleState, 70 | enter = fadeIn( 71 | animationSpec = tween(durationMillis = 500,easing = LinearOutSlowInEasing)) 72 | + slideInVertically(animationSpec = tween(durationMillis = 500)) 73 | ) { 74 | FloatingActionButton( 75 | onClick = { 76 | navController.navigate(Screen.AddEditNoteScreen.route) 77 | }, 78 | backgroundColor = MaterialTheme.colors.primary 79 | ) { 80 | Icon(imageVector = Icons.Default.Add, contentDescription = "Add") 81 | } 82 | } 83 | }, 84 | scaffoldState = scaffoldState, 85 | snackbarHost = { 86 | SnackbarHost(it) { data -> 87 | Snackbar( 88 | snackbarData = data, 89 | actionColor = MaterialTheme.colors.surface 90 | ) 91 | } 92 | } 93 | ) { 94 | Column( 95 | modifier = Modifier 96 | .fillMaxSize() 97 | .padding(it) 98 | .padding(top = 16.dp,start = 16.dp,end = 16.dp) 99 | ) { 100 | Row( 101 | modifier = Modifier.fillMaxWidth(), 102 | horizontalArrangement = Arrangement.SpaceBetween, 103 | verticalAlignment = Alignment.CenterVertically 104 | ) { 105 | Text( 106 | text = "Notes", 107 | style = TextStyle( 108 | color = MaterialTheme.typography.h4.color, 109 | fontStyle = MaterialTheme.typography.h4.fontStyle, 110 | fontSize = MaterialTheme.typography.h4.fontSize, 111 | fontWeight = MaterialTheme.typography.h4.fontWeight, 112 | fontFamily = customTypography.body1.fontFamily 113 | ) 114 | ) 115 | if (notesAvailable) { 116 | 117 | IconButton( 118 | onClick = { 119 | viewModel.onEvent(NotesEvent.ToggleOrderSection) 120 | } 121 | ) { 122 | Icon( 123 | imageVector = Icons.Default.Sort, 124 | contentDescription = "Sort", 125 | ) 126 | } 127 | } 128 | } 129 | AnimatedVisibility( 130 | visible = state.isOrderSectionVisible, 131 | enter = fadeIn() + slideInVertically(), 132 | exit = fadeOut() + slideOutVertically() 133 | ) { 134 | OrderSection( 135 | modifier = Modifier 136 | .fillMaxWidth() 137 | .padding(vertical = 16.dp) 138 | .testTag(TestTags.ORDER_SECTION), 139 | noteOrder = state.noteOrder, 140 | onOrderChange = { 141 | viewModel.onEvent(NotesEvent.Order(it)) 142 | } 143 | ) 144 | } 145 | Spacer(modifier = Modifier.height(16.dp)) 146 | 147 | if (notesAvailable) { 148 | LazyColumn(modifier = Modifier.fillMaxSize()) { 149 | 150 | items(state.notes) { note -> 151 | NoteItem( 152 | note = note, 153 | modifier = Modifier 154 | .fillMaxWidth() 155 | .clickable { 156 | navController.navigate( 157 | Screen.AddEditNoteScreen.route + 158 | "?noteId=${note.id}¬eColor=${note.color}" 159 | ) 160 | }, 161 | onDeleteClick = { 162 | viewModel.onEvent(NotesEvent.DeleteNote(note)) 163 | scope.launch { 164 | val result = scaffoldState.snackbarHostState.showSnackbar( 165 | message = "Note deleted", 166 | actionLabel = "Undo" 167 | ) 168 | if (result == SnackbarResult.ActionPerformed) { 169 | viewModel.onEvent(NotesEvent.RestoreNote) 170 | } 171 | } 172 | } 173 | ) 174 | Spacer(modifier = Modifier.height(16.dp)) 175 | if(state.notes.last() == note) { 176 | Spacer(modifier = Modifier.height(60.dp)) 177 | } 178 | } 179 | 180 | } 181 | } else { 182 | EmptyScreenText() 183 | } 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/NotesState.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes 2 | 3 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 4 | import com.akshayashokcode.notepad.feature_note.domain.util.NoteOrder 5 | import com.akshayashokcode.notepad.feature_note.domain.util.OrderType 6 | 7 | data class NotesState( 8 | val notes:List = emptyList(), 9 | val noteOrder: NoteOrder=NoteOrder.Date(OrderType.Descending), 10 | val isOrderSectionVisible:Boolean=false 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/NotesViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 8 | import com.akshayashokcode.notepad.feature_note.domain.use_case.NoteUseCases 9 | import com.akshayashokcode.notepad.feature_note.domain.util.NoteOrder 10 | import com.akshayashokcode.notepad.feature_note.domain.util.OrderType 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.Job 13 | import kotlinx.coroutines.flow.launchIn 14 | import kotlinx.coroutines.flow.onEach 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class NotesViewModel @Inject constructor( 20 | private val noteUseCases: NoteUseCases 21 | ) : ViewModel() { 22 | 23 | private val _state = mutableStateOf(NotesState()) 24 | val state: State = _state 25 | 26 | private var recentlyDeletedNote: Note? = null 27 | 28 | private var getNotesJob: Job? = null 29 | 30 | init { 31 | getNotes(NoteOrder.Date(OrderType.Descending)) 32 | 33 | } 34 | 35 | fun onEvent(event: NotesEvent) { 36 | when (event) { 37 | is NotesEvent.Order -> { 38 | if (state.value.noteOrder::class == event.noteOrder::class && 39 | state.value.noteOrder.orderType == event.noteOrder.orderType 40 | ) { 41 | return 42 | } 43 | getNotes(event.noteOrder) 44 | } 45 | is NotesEvent.DeleteNote -> { 46 | viewModelScope.launch { 47 | noteUseCases.deleteNote(event.note) 48 | recentlyDeletedNote = event.note 49 | } 50 | } 51 | is NotesEvent.RestoreNote -> { 52 | viewModelScope.launch { 53 | noteUseCases.addNote(recentlyDeletedNote ?: return@launch) 54 | recentlyDeletedNote = null 55 | } 56 | } 57 | is NotesEvent.ToggleOrderSection -> { 58 | _state.value = state.value.copy( 59 | isOrderSectionVisible = !state.value.isOrderSectionVisible 60 | ) 61 | } 62 | } 63 | } 64 | 65 | private fun getNotes(noteOrder: NoteOrder) { 66 | getNotesJob?.cancel() 67 | getNotesJob = noteUseCases.getNotes(noteOrder) 68 | .onEach { notes -> 69 | _state.value = state.value.copy( 70 | notes = notes, 71 | noteOrder = noteOrder 72 | ) 73 | } 74 | .launchIn(viewModelScope) 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/components/DefaultRadioButton.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes.components 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.width 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.RadioButton 8 | import androidx.compose.material.RadioButtonDefaults 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.semantics.contentDescription 14 | import androidx.compose.ui.semantics.semantics 15 | import androidx.compose.ui.unit.dp 16 | 17 | @Composable 18 | fun DefaultRadioButton( 19 | text:String, 20 | selected:Boolean, 21 | onSelect:()->Unit, 22 | modifier: Modifier =Modifier 23 | ) { 24 | Row( 25 | modifier = modifier, 26 | verticalAlignment = Alignment.CenterVertically 27 | ){ 28 | RadioButton( 29 | selected =selected, 30 | onClick = onSelect, 31 | colors = RadioButtonDefaults.colors( 32 | selectedColor = MaterialTheme.colors.primary, 33 | unselectedColor = MaterialTheme.colors.onBackground 34 | ), 35 | modifier = Modifier.semantics { 36 | contentDescription=text 37 | } 38 | ) 39 | Spacer(modifier = Modifier.width(8.dp)) 40 | Text(text = text,style = MaterialTheme.typography.body1) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/components/EmptyScreenText.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.text.TextStyle 11 | import androidx.compose.ui.text.font.FontFamily 12 | import androidx.compose.ui.unit.dp 13 | 14 | @Composable 15 | fun EmptyScreenText( 16 | modifier:Modifier=Modifier 17 | ) { 18 | Column( modifier = modifier.fillMaxSize(), 19 | horizontalAlignment = Alignment.CenterHorizontally, 20 | verticalArrangement = Arrangement.Center 21 | ) 22 | { 23 | Text(text ="No notes", 24 | style = TextStyle(color= Color.LightGray,fontStyle = MaterialTheme.typography.h6.fontStyle, 25 | fontSize = MaterialTheme.typography.h6.fontSize,fontWeight = MaterialTheme.typography.h6.fontWeight, 26 | fontFamily = FontFamily.SansSerif 27 | ) 28 | ) 29 | Text(text ="Tap the Add button to create a note", 30 | style = TextStyle(color=Color.Gray,fontStyle = MaterialTheme.typography.subtitle2.fontStyle, 31 | fontSize = MaterialTheme.typography.subtitle2.fontSize,fontWeight = MaterialTheme.typography.subtitle2.fontWeight, 32 | fontFamily = FontFamily.Default 33 | ) 34 | ) 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/components/NoteItem.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes.components 2 | 3 | import androidx.compose.foundation.Canvas 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material.Icon 6 | import androidx.compose.material.IconButton 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Text 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.Delete 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.geometry.CornerRadius 15 | import androidx.compose.ui.geometry.Offset 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.graphics.Path 18 | import androidx.compose.ui.graphics.drawscope.clipPath 19 | import androidx.compose.ui.platform.testTag 20 | import androidx.compose.ui.text.style.TextOverflow 21 | import androidx.compose.ui.unit.Dp 22 | import androidx.compose.ui.unit.dp 23 | import androidx.core.graphics.ColorUtils 24 | import com.akshayashokcode.notepad.core.util.TestTags 25 | import com.akshayashokcode.notepad.feature_note.domain.model.Note 26 | 27 | 28 | @Composable 29 | fun NoteItem( 30 | note: Note, 31 | modifier: Modifier = Modifier, 32 | cornerRadius: Dp = 10.dp, 33 | cutCornerSize: Dp = 30.dp, 34 | onDeleteClick: () -> Unit 35 | ) { 36 | Box(modifier = modifier.testTag(TestTags.NOTE_ITEM)) { 37 | Canvas(modifier = Modifier.matchParentSize()) { 38 | val clipPath = Path().apply { 39 | lineTo(size.width - cutCornerSize.toPx(), 0f) 40 | lineTo(size.width, cutCornerSize.toPx()) 41 | lineTo(size.width, size.height) 42 | lineTo(0f, size.height) 43 | close() 44 | } 45 | clipPath(clipPath) { 46 | drawRoundRect( 47 | color = Color(note.color), 48 | size = size, 49 | cornerRadius = CornerRadius(cornerRadius.toPx()) 50 | ) 51 | drawRoundRect( 52 | color = Color( 53 | ColorUtils.blendARGB(note.color, 0x000000, 0.2f) 54 | ), 55 | topLeft = Offset( 56 | size.width - cutCornerSize.toPx() + 100f, 57 | cutCornerSize.toPx() + 100f 58 | ), 59 | size = size, 60 | cornerRadius = CornerRadius(cornerRadius.toPx()) 61 | ) 62 | } 63 | } 64 | Column( 65 | modifier = Modifier 66 | .fillMaxSize() 67 | .padding(16.dp) 68 | .padding(end = 32.dp) 69 | ) { 70 | Text( 71 | text = note.title, 72 | style = MaterialTheme.typography.h6, 73 | color = MaterialTheme.colors.onSurface, 74 | maxLines = 1, 75 | overflow = TextOverflow.Ellipsis 76 | ) 77 | Spacer(modifier = Modifier.height(8.dp)) 78 | Text( 79 | text = note.content, 80 | style = MaterialTheme.typography.body1, 81 | color = MaterialTheme.colors.onSurface, 82 | maxLines = 10, 83 | overflow = TextOverflow.Ellipsis 84 | ) 85 | } 86 | IconButton( 87 | onClick = onDeleteClick, 88 | modifier = Modifier.align(Alignment.BottomEnd) 89 | ) { 90 | Icon( 91 | imageVector = Icons.Default.Delete, 92 | contentDescription = "Delete Note", 93 | tint=MaterialTheme.colors.onSurface 94 | ) 95 | 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/notes/components/OrderSection.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.notes.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.unit.dp 8 | import androidx.compose.ui.unit.sp 9 | import com.akshayashokcode.notepad.feature_note.domain.util.NoteOrder 10 | import com.akshayashokcode.notepad.feature_note.domain.util.OrderType 11 | 12 | 13 | @Composable 14 | fun OrderSection( 15 | modifier: Modifier = Modifier, 16 | noteOrder: NoteOrder = NoteOrder.Date(OrderType.Descending), 17 | onOrderChange: (NoteOrder) -> Unit 18 | ) { 19 | Column( 20 | modifier = modifier 21 | ) { 22 | Row( 23 | modifier = Modifier.fillMaxWidth() 24 | ) { 25 | Text(text = "Sort by:",fontSize = 16.sp) 26 | Spacer(modifier = Modifier.width(8.dp)) 27 | DefaultRadioButton( 28 | text = "Title", 29 | selected = noteOrder is NoteOrder.Title, 30 | onSelect = { onOrderChange(NoteOrder.Title(noteOrder.orderType))} 31 | ) 32 | Spacer(modifier = Modifier.width(8.dp)) 33 | DefaultRadioButton( 34 | text = "Date", 35 | selected = noteOrder is NoteOrder.Date, 36 | onSelect = { onOrderChange(NoteOrder.Date(noteOrder.orderType))} 37 | ) 38 | Spacer(modifier = Modifier.width(8.dp)) 39 | DefaultRadioButton( 40 | text = "Color", 41 | selected = noteOrder is NoteOrder.Color, 42 | onSelect = { onOrderChange(NoteOrder.Color(noteOrder.orderType))} 43 | ) 44 | 45 | } 46 | Spacer(modifier = Modifier.height(16.dp)) 47 | Row( 48 | modifier = Modifier.fillMaxWidth() 49 | ) { 50 | DefaultRadioButton( 51 | text = "Ascending↓", 52 | selected = noteOrder.orderType is OrderType.Ascending , 53 | onSelect = {onOrderChange(noteOrder.copy(OrderType.Ascending))} 54 | ) 55 | Spacer(modifier = Modifier.width(8.dp)) 56 | DefaultRadioButton( 57 | text = "Descending↑", 58 | selected = noteOrder.orderType is OrderType.Descending , 59 | onSelect = {onOrderChange(noteOrder.copy(OrderType.Descending))} 60 | ) 61 | 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/util/KeyBoardManager.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.graphics.Rect 6 | import android.view.View 7 | import android.view.ViewTreeObserver 8 | 9 | class KeyBoardManager(context: Context) { 10 | 11 | private val activity = context as Activity 12 | private var keyboardDismissListener: KeyboardDismissListener? = null 13 | 14 | private abstract class KeyboardDismissListener( 15 | private val rootView: View, 16 | private val onKeyboardDismiss: () -> Unit 17 | ) : ViewTreeObserver.OnGlobalLayoutListener { 18 | private var isKeyboardClosed: Boolean = false 19 | override fun onGlobalLayout() { 20 | val r = Rect() 21 | rootView.getWindowVisibleDisplayFrame(r) 22 | val screenHeight = rootView.rootView.height 23 | val keypadHeight = screenHeight - r.bottom 24 | if (keypadHeight > screenHeight * 0.15) { 25 | // 0.15 ratio is right enough to determine keypad height. 26 | isKeyboardClosed = false 27 | } else if (!isKeyboardClosed) { 28 | isKeyboardClosed = true 29 | onKeyboardDismiss.invoke() 30 | } 31 | } 32 | } 33 | fun attachKeyboardDismissListener(onKeyboardDismiss: () -> Unit) { 34 | val rootView = activity.findViewById(android.R.id.content) 35 | keyboardDismissListener = object : KeyboardDismissListener(rootView, onKeyboardDismiss) {} 36 | keyboardDismissListener?.let { 37 | rootView.viewTreeObserver.addOnGlobalLayoutListener(it) 38 | } 39 | } 40 | 41 | fun release() { 42 | val rootView = activity.findViewById(android.R.id.content) 43 | keyboardDismissListener?.let { 44 | rootView.viewTreeObserver.removeOnGlobalLayoutListener(it) 45 | } 46 | keyboardDismissListener = null 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/feature_note/presentation/util/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.feature_note.presentation.util 2 | 3 | sealed class Screen(val route:String){ 4 | data object NotesScreen:Screen("notes_screen") 5 | data object AddEditNoteScreen:Screen("add_edit_note_screen") 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val DarkGray = Color(0xFF202020) 6 | val LightGray = Color(0xFF444040) 7 | val LightBlue = Color(0xFFD7E8DE) 8 | 9 | val RedOrange = Color(0xffffab91) 10 | val RedPink = Color(0xfff48fb1) 11 | val BabyBlue = Color(0xff81deea) 12 | val Violet = Color(0xffcf94da) 13 | val LightGreen = Color(0xffe7ed9b) -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 10 | 11 | private val DarkColorPalette = darkColors( 12 | primary = Color.White, 13 | background = DarkGray, 14 | onBackground = Color.White, 15 | surface = LightBlue, 16 | onSurface = DarkGray 17 | ) 18 | 19 | private val LightColorPalette = lightColors( 20 | primary = LightGray, 21 | background = LightBlue, 22 | onBackground = Color.Black, 23 | surface = Color.White, 24 | onSurface = Color.Black 25 | ) 26 | @Composable 27 | fun NotePadTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { 28 | val colors=if(darkTheme){ 29 | rememberSystemUiController().setSystemBarsColor( 30 | color = Color.Transparent 31 | ) 32 | DarkColorPalette 33 | }else{ 34 | rememberSystemUiController().setSystemBarsColor( 35 | color = Color.White 36 | ) 37 | LightColorPalette 38 | } 39 | 40 | 41 | MaterialTheme( 42 | colors = colors, 43 | typography = Typography, 44 | shapes = Shapes, 45 | content = content 46 | ) 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/akshayashokcode/notepad/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.akshayashokcode.notepad.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import com.akshayashokcode.notepad.R 10 | 11 | // Set of Material typography styles to start with 12 | val Typography = Typography( 13 | body1 = TextStyle( 14 | fontFamily = FontFamily.Default, 15 | fontWeight = FontWeight.Normal, 16 | fontSize = 16.sp 17 | ) 18 | 19 | /* Other default text styles to override 20 | button = TextStyle( 21 | fontFamily = FontFamily.Default, 22 | fontWeight = FontWeight.W500, 23 | fontSize = 14.sp 24 | ), 25 | caption = TextStyle( 26 | fontFamily = FontFamily.Default, 27 | fontWeight = FontWeight.Normal, 28 | fontSize = 12.sp 29 | ) 30 | */ 31 | ) 32 | val customTypography = Typography( 33 | body1 = TextStyle( 34 | fontFamily = FontFamily( Font(R.font.varela_round_regular)), 35 | fontWeight = FontWeight.Normal, 36 | fontSize = 16.sp 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/notepad_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/drawable/notepad_icon.png -------------------------------------------------------------------------------- /app/src/main/res/font/varela_round_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/font/varela_round_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkshayAshokCode/NotePad/32a029d54d3f1c9152486f52eb39566c2f5fa40c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF202020 5 | #FF202020 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3DDC84 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NotePad 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |