├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── gradle.xml ├── misc.xml ├── sonarlint │ └── issuestore │ │ ├── 0 │ │ ├── a │ │ │ └── 0a347c2b22731d68c28b734fdee4846dbce801ac │ │ └── e │ │ │ └── 0e7e206f2ef4ca898cc1c6f9c43509c2fda87a46 │ │ ├── 1 │ │ ├── 5 │ │ │ └── 15f87573f2562809f55b4301227653a12fbd9ab9 │ │ └── c │ │ │ └── 1c207e90608557c98eca3b54458ee0eada3295f7 │ │ ├── 3 │ │ └── b │ │ │ └── 3b7c6a8d1a8fd5e96594f7d90e01a4c3359f1642 │ │ ├── 4 │ │ ├── 2 │ │ │ └── 42074a9a394d39e1bd035d39c39e9fc65da3ceca │ │ ├── 4 │ │ │ └── 4441730111cf49bcd402fa82db8e034f9671b584 │ │ └── e │ │ │ ├── 4e42000e473f66e96cc60d6eeaadcf9bfda3041e │ │ │ └── 4e779135634d0188e6f5f509ab7c317d5416da3a │ │ ├── 5 │ │ └── 9 │ │ │ └── 59d2e248db7a62e8b2be3c9c58faa64c30785f4d │ │ ├── 7 │ │ ├── 0 │ │ │ └── 70dc84dcb33fd7fb7f4419f7bb1ef84b246ea625 │ │ └── 3 │ │ │ └── 73ccd3dd938dc14c03197221d9f58927d1a489fa │ │ ├── 8 │ │ ├── 5 │ │ │ └── 85eb642c59a8bc10cf60b7f81ae9f3c7be070e07 │ │ ├── 9 │ │ │ ├── 89443d9b758e1f297c019b03131d50d594a36c6b │ │ │ └── 894a81455c4de60975f3026a8743156115a134d3 │ │ ├── c │ │ │ └── 8c55c3ccc257e5907959013f99656e4c8ec3903e │ │ └── d │ │ │ └── 8db0821229949e8f2a589d10bc7ea9a135da6c8e │ │ ├── 9 │ │ └── d │ │ │ └── 9d1b1002fe90e56522647df3334da3b1b7bd68e3 │ │ ├── a │ │ └── a │ │ │ └── aa8bff76e11859170c4a52d2983d4f1a4fad9417 │ │ ├── b │ │ └── 6 │ │ │ └── b697311ca27d5c6645f75c4077cf3eb564cf6ed1 │ │ ├── c │ │ ├── 1 │ │ │ └── c127b77fc102fc3fda632a76ddc99d4ccf7a10d5 │ │ ├── 3 │ │ │ └── c33522a5f4d7f9914d0112918f975f2d45b30441 │ │ ├── 5 │ │ │ └── c577566c98eb6117440fee6c0f306f715d60d91e │ │ └── a │ │ │ └── ca9e34c62628440135fb142a1fba5f028ec2359b │ │ ├── d │ │ └── 8 │ │ │ └── d883e197a3fa06a093ef00c6b5d9a5ef7510e3e0 │ │ ├── e │ │ └── 1 │ │ │ └── e143825a3c2a2113b7aa40f2393d0114f79190e9 │ │ ├── f │ │ ├── 0 │ │ │ └── f07866736216be0ee2aba49e392191aeae700a35 │ │ ├── 4 │ │ │ └── f4a01d6a4fcb971362ec00a83903fd3902f52164 │ │ ├── c │ │ │ ├── fc90888977fdecd39953e843c6e3c6c7fd123ed2 │ │ │ └── fca1737f836c684f59ec001226079c63e8ebd8ce │ │ └── f │ │ │ └── ff8ad05914c6aa4ce7fb60c92125f63a8a5ef08b │ │ └── index.pb └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── notesapp │ │ ├── ExampleInstrumentedTest.kt │ │ ├── HiltTestRunner.kt │ │ ├── di │ │ └── TestAppModule.kt │ │ └── feature_note │ │ └── presentation │ │ └── notes │ │ └── NoteScreenTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── notesapp │ │ │ ├── NotesApp.kt │ │ │ ├── di │ │ │ └── AppModule.kt │ │ │ ├── feature_note │ │ │ ├── data │ │ │ │ ├── data_source │ │ │ │ │ ├── NoteDao.kt │ │ │ │ │ └── NoteDataBase.kt │ │ │ │ └── repository │ │ │ │ │ └── NoteRepositoryImp.kt │ │ │ ├── domain │ │ │ │ ├── model │ │ │ │ │ └── Note.kt │ │ │ │ ├── repository │ │ │ │ │ └── NoteRepository.kt │ │ │ │ ├── use_case │ │ │ │ │ ├── AddNotes.kt │ │ │ │ │ ├── DeleteNotes.kt │ │ │ │ │ ├── GetNote.kt │ │ │ │ │ ├── GetNotes.kt │ │ │ │ │ └── NoteUseCases.kt │ │ │ │ └── util │ │ │ │ │ ├── NoteOrder.kt │ │ │ │ │ └── OrderType.kt │ │ │ └── presentation │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── add_edit_notes │ │ │ │ ├── AddEditNoteEvent.kt │ │ │ │ ├── AddEditNoteScreen.kt │ │ │ │ ├── AddEditNoteViewModel.kt │ │ │ │ ├── NoteTextFieldState.kt │ │ │ │ └── components │ │ │ │ │ └── TransParentHintTextField.kt │ │ │ │ ├── notes │ │ │ │ ├── NoteScreen.kt │ │ │ │ ├── NotesEvent.kt │ │ │ │ ├── NotesState.kt │ │ │ │ ├── NotesViewModel.kt │ │ │ │ └── components │ │ │ │ │ ├── DefaultRadioButton.kt │ │ │ │ │ ├── NoteItem.kt │ │ │ │ │ └── OrderSection.kt │ │ │ │ └── util │ │ │ │ └── Screen.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── notesapp │ ├── ExampleUnitTest.kt │ └── feature_note │ ├── data │ └── repository │ │ └── FakeNoteRepository.kt │ └── domain │ └── use_case │ └── GetNotesTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/0/a/0a347c2b22731d68c28b734fdee4846dbce801ac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/0/a/0a347c2b22731d68c28b734fdee4846dbce801ac -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/0/e/0e7e206f2ef4ca898cc1c6f9c43509c2fda87a46: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/0/e/0e7e206f2ef4ca898cc1c6f9c43509c2fda87a46 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/1/5/15f87573f2562809f55b4301227653a12fbd9ab9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/1/5/15f87573f2562809f55b4301227653a12fbd9ab9 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/1/c/1c207e90608557c98eca3b54458ee0eada3295f7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/1/c/1c207e90608557c98eca3b54458ee0eada3295f7 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/3/b/3b7c6a8d1a8fd5e96594f7d90e01a4c3359f1642: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/3/b/3b7c6a8d1a8fd5e96594f7d90e01a4c3359f1642 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/4/2/42074a9a394d39e1bd035d39c39e9fc65da3ceca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/4/2/42074a9a394d39e1bd035d39c39e9fc65da3ceca -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/4/4/4441730111cf49bcd402fa82db8e034f9671b584: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/4/4/4441730111cf49bcd402fa82db8e034f9671b584 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/4/e/4e42000e473f66e96cc60d6eeaadcf9bfda3041e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/4/e/4e42000e473f66e96cc60d6eeaadcf9bfda3041e -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/4/e/4e779135634d0188e6f5f509ab7c317d5416da3a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/4/e/4e779135634d0188e6f5f509ab7c317d5416da3a -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/5/9/59d2e248db7a62e8b2be3c9c58faa64c30785f4d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/5/9/59d2e248db7a62e8b2be3c9c58faa64c30785f4d -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/7/0/70dc84dcb33fd7fb7f4419f7bb1ef84b246ea625: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/7/0/70dc84dcb33fd7fb7f4419f7bb1ef84b246ea625 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/7/3/73ccd3dd938dc14c03197221d9f58927d1a489fa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/7/3/73ccd3dd938dc14c03197221d9f58927d1a489fa -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/5/85eb642c59a8bc10cf60b7f81ae9f3c7be070e07: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/8/5/85eb642c59a8bc10cf60b7f81ae9f3c7be070e07 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/9/89443d9b758e1f297c019b03131d50d594a36c6b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/8/9/89443d9b758e1f297c019b03131d50d594a36c6b -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/9/894a81455c4de60975f3026a8743156115a134d3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/8/9/894a81455c4de60975f3026a8743156115a134d3 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/c/8c55c3ccc257e5907959013f99656e4c8ec3903e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/8/c/8c55c3ccc257e5907959013f99656e4c8ec3903e -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/d/8db0821229949e8f2a589d10bc7ea9a135da6c8e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/8/d/8db0821229949e8f2a589d10bc7ea9a135da6c8e -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/9/d/9d1b1002fe90e56522647df3334da3b1b7bd68e3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/9/d/9d1b1002fe90e56522647df3334da3b1b7bd68e3 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/a/a/aa8bff76e11859170c4a52d2983d4f1a4fad9417: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/a/a/aa8bff76e11859170c4a52d2983d4f1a4fad9417 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/b/6/b697311ca27d5c6645f75c4077cf3eb564cf6ed1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/b/6/b697311ca27d5c6645f75c4077cf3eb564cf6ed1 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/c/1/c127b77fc102fc3fda632a76ddc99d4ccf7a10d5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/c/1/c127b77fc102fc3fda632a76ddc99d4ccf7a10d5 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/c/3/c33522a5f4d7f9914d0112918f975f2d45b30441: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/c/3/c33522a5f4d7f9914d0112918f975f2d45b30441 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/c/5/c577566c98eb6117440fee6c0f306f715d60d91e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/c/5/c577566c98eb6117440fee6c0f306f715d60d91e -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/c/a/ca9e34c62628440135fb142a1fba5f028ec2359b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/c/a/ca9e34c62628440135fb142a1fba5f028ec2359b -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/d/8/d883e197a3fa06a093ef00c6b5d9a5ef7510e3e0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/d/8/d883e197a3fa06a093ef00c6b5d9a5ef7510e3e0 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/e/1/e143825a3c2a2113b7aa40f2393d0114f79190e9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/e/1/e143825a3c2a2113b7aa40f2393d0114f79190e9 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/f/0/f07866736216be0ee2aba49e392191aeae700a35: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/f/0/f07866736216be0ee2aba49e392191aeae700a35 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/f/4/f4a01d6a4fcb971362ec00a83903fd3902f52164: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/f/4/f4a01d6a4fcb971362ec00a83903fd3902f52164 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/f/c/fc90888977fdecd39953e843c6e3c6c7fd123ed2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/f/c/fc90888977fdecd39953e843c6e3c6c7fd123ed2 -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/f/c/fca1737f836c684f59ec001226079c63e8ebd8ce: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/f/c/fca1737f836c684f59ec001226079c63e8ebd8ce -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/f/f/ff8ad05914c6aa4ce7fb60c92125f63a8a5ef08b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/f/f/ff8ad05914c6aa4ce7fb60c92125f63a8a5ef08b -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/index.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/.idea/sonarlint/issuestore/index.pb -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Notes App Using Jetpack Compose

2 | 3 |
[download apk from here](https://github.com/ELTEGANI/NotesApp/files/7609684/apk.zip)
4 | 5 | 6 | 7 | 8 | - [MVVM](https://developer.android.com/jetpack/guide?gclid=CjwKCAjwp_GJBhBmEiwALWBQk1owtAt-_GofEWOEfTFbUSnFwVrSBYjPI79ne6TNg6hgdIBrKlghMBoCD10QAvD_BwE&gclsrc=aw.ds) MVVM Architecture (Model - View - ViewModel) 9 | - [Clean-architecture](https://www.raywenderlich.com/3595916-clean-architecture-tutorial-for-android-getting-started) MVVM with Clean Architecture is pretty good in such cases. It goes one step further in separating the responsibilities of your code base. 10 | - [Jetpack Compose](https://developer.android.com/jetpack/compose?gclid=CjwKCAjwp_GJBhBmEiwALWBQky8AeNwcOvTsnGOMhZwBOKXNtzEV0rYpLRchJpoMeaJ2pbYp-JxzbxoCS1oQAvD_BwE&gclsrc=aw.ds) Modern design practices, Jetpack libraries enable fewer crashes and memory leaks. Describe your UI. 11 | - [kotlin](https://developer.android.com/kotlin?gclid=CjwKCAjwp_GJBhBmEiwALWBQkxkmTa4zClljSKWZm5xUmt5cWJo0zE4f24zmZW1uVLP6cHeF9BnjOxoCRYEQAvD_BwE&gclsrc=aw.ds) Modern, concise and safe programming language. Easy to pick up, so you can create powerful applications immediately 12 | - [Dagger - Hilt](https://developer.android.com/training/dependency-injection/hilt-android) dependency injection. 13 | - [Room](https://developer.android.com/jetpack/androidx/releases/room) Room persistence library. 14 | - [navigation](https://developer.android.com/guide/navigation?gclid=CjwKCAjwp_GJBhBmEiwALWBQk-LCvnuj3tWA07HX5cXG6p3HHjNnmH2Ong6Lk-_3sTGxh3yiW9Z3rRoCzLgQAvD_BwE&gclsrc=aw.ds) Android Jetpack's Navigation component helps you implement navigation 15 | - [flow](https://developer.android.com/kotlin/flow) notify domain layer data to views. 16 | - [coroutines](https://developer.android.com/kotlin/coroutines?gclid=CjwKCAjwp_GJBhBmEiwALWBQk4flWebxYMw9cmq_-zbKtyjEHLkwa6n_LgA5QWEm9lFwXJJPlXdgVxoCaB0QAvD_BwE&gclsrc=aw.ds) A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'dagger.hilt.android.plugin' 5 | id 'kotlin-kapt' 6 | } 7 | 8 | android { 9 | compileSdk 31 10 | defaultConfig { 11 | applicationId "com.example.notesapp" 12 | minSdk 21 13 | targetSdk 31 14 | versionCode 1 15 | versionName "1.0" 16 | testInstrumentationRunner "com.example.notesapp.HiltTestRunner" 17 | vectorDrawables { 18 | useSupportLibrary true 19 | } 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | useIR = true 35 | } 36 | buildFeatures { 37 | compose true 38 | } 39 | composeOptions { 40 | kotlinCompilerExtensionVersion compose_version 41 | kotlinCompilerVersion '1.5.10' 42 | } 43 | packagingOptions { 44 | resources { 45 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 46 | } 47 | } 48 | } 49 | 50 | dependencies { 51 | 52 | implementation 'androidx.core:core-ktx:1.7.0' 53 | implementation 'androidx.appcompat:appcompat:1.4.0' 54 | implementation 'com.google.android.material:material:1.4.0' 55 | implementation "androidx.compose.ui:ui:$compose_version" 56 | implementation "androidx.compose.material:material:$compose_version" 57 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 58 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' 59 | implementation 'androidx.activity:activity-compose:1.4.0' 60 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 61 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 62 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 63 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 64 | 65 | // Compose dependencies 66 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0" 67 | implementation "androidx.navigation:navigation-compose:2.4.0-beta02" 68 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 69 | implementation "androidx.hilt:hilt-navigation-compose:1.0.0-beta01" 70 | 71 | // Coroutines 72 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' 73 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' 74 | 75 | //Dagger - Hilt 76 | implementation "com.google.dagger:hilt-android:2.38.1" 77 | kapt "com.google.dagger:hilt-android-compiler:2.37" 78 | implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" 79 | kapt "androidx.hilt:hilt-compiler:1.0.0" 80 | 81 | // Room 82 | implementation "androidx.room:room-runtime:2.3.0" 83 | kapt "androidx.room:room-compiler:2.3.0" 84 | 85 | // Kotlin Extensions and Coroutines support for Room 86 | implementation "androidx.room:room-ktx:2.3.0" 87 | 88 | // Local unit tests 89 | testImplementation "androidx.test:core:1.4.0" 90 | testImplementation "junit:junit:4.13.2" 91 | testImplementation "androidx.arch.core:core-testing:2.1.0" 92 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2" 93 | testImplementation "com.google.truth:truth:1.1.3" 94 | testImplementation "com.squareup.okhttp3:mockwebserver:4.9.1" 95 | testImplementation "io.mockk:mockk:1.10.5" 96 | debugImplementation "androidx.compose.ui:ui-test-manifest:1.1.0-beta03" 97 | 98 | // Instrumentation tests 99 | androidTestImplementation 'com.google.dagger:hilt-android-testing:2.37' 100 | kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.37' 101 | androidTestImplementation "junit:junit:4.13.2" 102 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2" 103 | androidTestImplementation "androidx.arch.core:core-testing:2.1.0" 104 | androidTestImplementation "com.google.truth:truth:1.1.3" 105 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 106 | androidTestImplementation 'androidx.test:core-ktx:1.4.0' 107 | androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.9.1" 108 | androidTestImplementation "io.mockk:mockk-android:1.10.5" 109 | androidTestImplementation 'androidx.test:runner:1.4.0' 110 | } -------------------------------------------------------------------------------- /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/example/notesapp/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp 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.example.notesapp", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/notesapp/HiltTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp 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 | 9 | class HiltTestRunner : AndroidJUnitRunner(){ 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/example/notesapp/di/TestAppModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.di 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.example.notesapp.feature_note.data.data_source.NoteDataBase 6 | import com.example.notesapp.feature_note.data.repository.NoteRepositoryImp 7 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 8 | import com.example.notesapp.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 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object TestAppModule { 19 | @Provides 20 | @Singleton 21 | fun provideNoteDataBase(application: Application):NoteDataBase{ 22 | return Room.inMemoryDatabaseBuilder(application,NoteDataBase::class.java) 23 | .build() 24 | } 25 | 26 | @Provides 27 | @Singleton 28 | fun provideNoteRepository(noteDataBase: NoteDataBase):NoteRepository{ 29 | return NoteRepositoryImp(noteDataBase.noteDao) 30 | } 31 | 32 | @Provides 33 | @Singleton 34 | fun provideNoteUseCase(noteRepository: NoteRepository):NoteUseCases{ 35 | return NoteUseCases( 36 | getNotes = GetNotes(noteRepository), 37 | deleteNotes = DeleteNotes(noteRepository), 38 | addNote = AddNotes(noteRepository), 39 | getNote = GetNote(noteRepository) 40 | ) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/notesapp/feature_note/presentation/notes/NoteScreenTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.notes 2 | 3 | import androidx.compose.animation.ExperimentalAnimationApi 4 | import androidx.compose.ui.test.junit4.createAndroidComposeRule 5 | import androidx.navigation.compose.NavHost 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.compose.rememberNavController 8 | import com.example.notesapp.di.AppModule 9 | import com.example.notesapp.feature_note.presentation.MainActivity 10 | import com.example.notesapp.feature_note.presentation.util.Screen 11 | import com.example.notesapp.ui.theme.CleanArchitectureNoteAppTheme 12 | import dagger.hilt.android.testing.HiltAndroidRule 13 | import dagger.hilt.android.testing.HiltAndroidTest 14 | import dagger.hilt.android.testing.UninstallModules 15 | import junit.framework.TestCase 16 | import org.junit.Before 17 | import org.junit.Rule 18 | 19 | 20 | @HiltAndroidTest 21 | @UninstallModules(AppModule::class) 22 | class NoteScreenTest{ 23 | @get:Rule(order = 0) 24 | val hiltRule = HiltAndroidRule(this) 25 | 26 | @get:Rule(order = 1) 27 | val composeRule = createAndroidComposeRule() 28 | 29 | @ExperimentalAnimationApi 30 | @Before 31 | fun setUp(){ 32 | hiltRule.inject() 33 | composeRule.setContent { 34 | val navController = rememberNavController() 35 | CleanArchitectureNoteAppTheme{ 36 | NavHost(navController = navController, startDestination = Screen.NotesScreen.route){ 37 | composable(route = Screen.NotesScreen.route) { NotesScreen(navController = navController) } 38 | } 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/NotesApp.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class NotesApp:Application() -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.di 2 | 3 | import android.app.Application 4 | import androidx.room.Room 5 | import com.example.notesapp.feature_note.data.data_source.NoteDataBase 6 | import com.example.notesapp.feature_note.data.repository.NoteRepositoryImp 7 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 8 | import com.example.notesapp.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 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object AppModule { 19 | @Provides 20 | @Singleton 21 | fun provideNoteDataBase(application: Application):NoteDataBase{ 22 | return Room.databaseBuilder(application,NoteDataBase::class.java,NoteDataBase.DATABASE_NAME) 23 | .build() 24 | } 25 | 26 | @Provides 27 | @Singleton 28 | fun provideNoteRepository(noteDataBase: NoteDataBase):NoteRepository{ 29 | return NoteRepositoryImp(noteDataBase.noteDao) 30 | } 31 | 32 | @Provides 33 | @Singleton 34 | fun provideNoteUseCase(noteRepository: NoteRepository):NoteUseCases{ 35 | return NoteUseCases( 36 | getNotes = GetNotes(noteRepository), 37 | deleteNotes = DeleteNotes(noteRepository), 38 | addNote = AddNotes(noteRepository), 39 | getNote = GetNote(noteRepository) 40 | ) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/data/data_source/NoteDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.data.data_source 2 | 3 | import androidx.room.* 4 | import com.example.notesapp.feature_note.domain.model.Note 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | 8 | @Dao 9 | interface NoteDao { 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/example/notesapp/feature_note/data/data_source/NoteDataBase.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.data.data_source 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.example.notesapp.feature_note.domain.model.Note 6 | 7 | 8 | @Database( 9 | entities = [Note::class], 10 | version = 1 11 | ) 12 | abstract class NoteDataBase : RoomDatabase() { 13 | abstract val noteDao:NoteDao 14 | 15 | companion object{ 16 | const val DATABASE_NAME = "notes_db" 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/data/repository/NoteRepositoryImp.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.data.repository 2 | 3 | import com.example.notesapp.feature_note.data.data_source.NoteDao 4 | import com.example.notesapp.feature_note.domain.model.Note 5 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class NoteRepositoryImp( 9 | private val noteDao: NoteDao 10 | ) : NoteRepository{ 11 | 12 | override fun getNotes(): Flow> { 13 | return noteDao.getNotes() 14 | } 15 | 16 | override suspend fun getNoteById(id: Int): Note? { 17 | return noteDao.getNoteById(id) 18 | } 19 | 20 | override suspend fun insertNote(note: Note) { 21 | noteDao.insertNote(note) 22 | } 23 | 24 | override suspend fun deleteNote(note: Note) { 25 | noteDao.deleteNote(note) 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/model/Note.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.model 2 | 3 | import androidx.compose.ui.graphics.Color 4 | import androidx.compose.ui.graphics.Color.Companion.LightGray 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import com.example.notesapp.ui.theme.BabyBlue 8 | import com.example.notesapp.ui.theme.RedOrange 9 | import com.example.notesapp.ui.theme.RedPink 10 | import com.example.notesapp.ui.theme.Violet 11 | import java.lang.Exception 12 | 13 | 14 | @Entity 15 | data class Note( 16 | val title:String, 17 | val content:String, 18 | val timesStamp:String, 19 | val color:Int, 20 | @PrimaryKey val id: Int? = null 21 | ){ 22 | companion object{ 23 | val noteColors = listOf(RedOrange,LightGray, Violet, BabyBlue, RedPink) 24 | } 25 | } 26 | 27 | class InvalidNoteException(message:String):Exception(message) -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/repository/NoteRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.repository 2 | 3 | import com.example.notesapp.feature_note.domain.model.Note 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | 7 | interface NoteRepository { 8 | fun getNotes():Flow> 9 | suspend fun getNoteById(id:Int):Note? 10 | suspend fun insertNote(note: Note) 11 | suspend fun deleteNote(note: Note) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/use_case/AddNotes.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.use_case 2 | 3 | import com.example.notesapp.feature_note.domain.model.InvalidNoteException 4 | import com.example.notesapp.feature_note.domain.model.Note 5 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 6 | import kotlin.jvm.Throws 7 | 8 | 9 | class AddNotes( 10 | private val notesRepository: NoteRepository 11 | ){ 12 | 13 | @Throws(InvalidNoteException::class) 14 | suspend operator fun invoke(note:Note){ 15 | if(note.title.isBlank()){ 16 | throw InvalidNoteException("The title of the note can't empty") 17 | } 18 | if(note.content.isBlank()){ 19 | throw InvalidNoteException("The content of the note can't empty") 20 | } 21 | notesRepository.insertNote(note) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/use_case/DeleteNotes.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.use_case 2 | 3 | import com.example.notesapp.feature_note.domain.model.Note 4 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 5 | 6 | class DeleteNotes(private val repository: NoteRepository) { 7 | suspend operator fun invoke(note:Note){ 8 | repository.deleteNote(note) 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/use_case/GetNote.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.use_case 2 | 3 | import com.example.notesapp.feature_note.domain.model.Note 4 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 5 | 6 | class GetNote( 7 | private val noteRepository: NoteRepository 8 | ) { 9 | suspend operator fun invoke(id:Int): Note?{ 10 | return noteRepository.getNoteById(id) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/use_case/GetNotes.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.use_case 2 | import com.example.notesapp.feature_note.domain.model.Note 3 | import com.example.notesapp.feature_note.domain.repository.NoteRepository 4 | import com.example.notesapp.feature_note.domain.util.NoteOrder 5 | import com.example.notesapp.feature_note.domain.util.OrderType 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.map 8 | 9 | 10 | class GetNotes ( 11 | private val repository: NoteRepository 12 | ){ 13 | operator fun invoke(noteOrder: NoteOrder = NoteOrder.Date(OrderType.Descending)): Flow> { 14 | return repository.getNotes().map {notes-> 15 | when(noteOrder.orderType){ 16 | is OrderType.Ascending->{ 17 | when(noteOrder){ 18 | is NoteOrder.Title -> notes.sortedBy { it.title.lowercase() } 19 | is NoteOrder.Date -> notes.sortedBy { it.timesStamp } 20 | is NoteOrder.Color -> notes.sortedBy { it.color } 21 | } 22 | } 23 | is OrderType.Descending->{ 24 | when(noteOrder){ 25 | is NoteOrder.Title -> notes.sortedByDescending { it.title.lowercase() } 26 | is NoteOrder.Date -> notes.sortedByDescending { it.timesStamp } 27 | is NoteOrder.Color -> notes.sortedByDescending { it.color } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/use_case/NoteUseCases.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.use_case 2 | 3 | 4 | 5 | data class NoteUseCases( 6 | val getNotes: GetNotes, 7 | val deleteNotes: DeleteNotes, 8 | val addNote:AddNotes, 9 | val getNote: GetNote 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/util/NoteOrder.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.util 2 | 3 | 4 | sealed class NoteOrder(var orderType: OrderType){ 5 | class Title(orderType: OrderType):NoteOrder(orderType) 6 | class Date(orderType: OrderType):NoteOrder(orderType) 7 | class Color(orderType: OrderType):NoteOrder(orderType) 8 | 9 | fun copy(orderType: OrderType): NoteOrder{ 10 | return when(this){ 11 | is Title -> Title(orderType) 12 | is Date -> Date(orderType) 13 | is Color -> Color(orderType) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/domain/util/OrderType.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.domain.util 2 | 3 | 4 | sealed class OrderType{ 5 | object Ascending:OrderType() 6 | object Descending:OrderType() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.animation.ExperimentalAnimationApi 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Surface 9 | import androidx.navigation.NavType 10 | import androidx.navigation.compose.NavHost 11 | import androidx.navigation.compose.composable 12 | import androidx.navigation.compose.rememberNavController 13 | import androidx.navigation.navArgument 14 | import com.example.notesapp.feature_note.presentation.add_edit_notes.AddEditNoteScreen 15 | import com.example.notesapp.feature_note.presentation.notes.NotesScreen 16 | import com.example.notesapp.feature_note.presentation.util.Screen 17 | import com.example.notesapp.ui.theme.CleanArchitectureNoteAppTheme 18 | import dagger.hilt.android.AndroidEntryPoint 19 | 20 | 21 | 22 | @AndroidEntryPoint 23 | class MainActivity : ComponentActivity() { 24 | @ExperimentalAnimationApi 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | setContent { 28 | CleanArchitectureNoteAppTheme { 29 | Surface( 30 | color = MaterialTheme.colors.background 31 | ) { 32 | val navController = rememberNavController() 33 | NavHost( 34 | navController = navController, 35 | startDestination = Screen.NotesScreen.route){ 36 | composable(route = Screen.NotesScreen.route){ 37 | NotesScreen(navController = navController) 38 | } 39 | composable(route = Screen.AddEditNoteScreen.route + "?noteId={noteId}¬eColor={noteColor}", 40 | arguments = listOf( 41 | navArgument(name = "noteId"){ 42 | type = NavType.IntType 43 | defaultValue = -1 44 | }, 45 | navArgument(name = "noteColor"){ 46 | type = NavType.IntType 47 | defaultValue = -1 48 | }, 49 | ) 50 | ){ 51 | val color = it.arguments?.getInt("noteColor") ?: -1 52 | AddEditNoteScreen(navController = navController, noteColor = color) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/add_edit_notes/AddEditNoteEvent.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.add_edit_notes 2 | 3 | import androidx.compose.ui.focus.FocusState 4 | 5 | 6 | sealed class AddEditNoteEvent { 7 | data class EnteredTitle(val value:String):AddEditNoteEvent() 8 | data class ChangeTitleFocus(val focus:FocusState):AddEditNoteEvent() 9 | data class EnteredContent(val value:String):AddEditNoteEvent() 10 | data class ChangeContentFocus(val focusState:FocusState):AddEditNoteEvent() 11 | data class changeColor(val color:Int):AddEditNoteEvent() 12 | object saveNote : AddEditNoteEvent() 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/add_edit_notes/AddEditNoteScreen.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.add_edit_notes 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.* 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.clip 16 | import androidx.compose.ui.draw.shadow 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.graphics.toArgb 19 | import androidx.compose.ui.unit.dp 20 | import androidx.hilt.navigation.compose.hiltViewModel 21 | import androidx.navigation.NavController 22 | import com.example.notesapp.feature_note.domain.model.Note 23 | import com.example.notesapp.feature_note.presentation.add_edit_notes.components.TransparentHintTextField 24 | import kotlinx.coroutines.flow.collectLatest 25 | import kotlinx.coroutines.launch 26 | 27 | 28 | @Composable 29 | fun AddEditNoteScreen( 30 | navController: NavController, 31 | noteColor:Int, 32 | viewModel: AddEditNoteViewModel = hiltViewModel() 33 | ){ 34 | val titleState = viewModel.noteTitle.value 35 | val contentState = viewModel.noteContent.value 36 | val scaffoldState = rememberScaffoldState() 37 | val noteBackgroundAnimatable = remember { 38 | Animatable( 39 | Color(if(noteColor != -1) noteColor else viewModel.noteColor.value) 40 | ) 41 | } 42 | val scope = rememberCoroutineScope() 43 | LaunchedEffect(key1 = true){ 44 | viewModel.eventFlow.collectLatest {event-> 45 | when(event){ 46 | is AddEditNoteViewModel.UiEvent.ShowSnackbar ->{ 47 | scaffoldState.snackbarHostState.showSnackbar( 48 | message = event.message 49 | ) 50 | } 51 | 52 | is AddEditNoteViewModel.UiEvent.SaveNote ->{ 53 | navController.navigateUp() 54 | } 55 | 56 | } 57 | } 58 | } 59 | Scaffold ( 60 | floatingActionButton = { 61 | FloatingActionButton(onClick = { 62 | viewModel.onEvent(AddEditNoteEvent.saveNote) 63 | } 64 | ,backgroundColor = MaterialTheme.colors.primary) { 65 | Icon(imageVector = Icons.Default.Save, contentDescription ="Save Note") 66 | } 67 | },scaffoldState = scaffoldState 68 | ){ 69 | Column ( 70 | modifier = Modifier 71 | .fillMaxSize() 72 | .background(noteBackgroundAnimatable.value) 73 | .padding(16.dp) 74 | ){ 75 | Row( 76 | modifier = Modifier 77 | .fillMaxWidth() 78 | .padding(8.dp), 79 | horizontalArrangement = Arrangement.SpaceBetween 80 | ) { 81 | Note.noteColors.forEach {color-> 82 | val colorInt = color.toArgb() 83 | Box( 84 | modifier = Modifier 85 | .size(50.dp) 86 | .shadow(15.dp, CircleShape) 87 | .clip(CircleShape) 88 | .background(color) 89 | .border( 90 | width = 3.dp, 91 | color = if (viewModel.noteColor.value == colorInt) { 92 | Color.Black 93 | } else Color.Transparent, 94 | shape = CircleShape 95 | ) 96 | .clickable { 97 | scope.launch { 98 | noteBackgroundAnimatable.animateTo( 99 | targetValue = Color(colorInt), 100 | animationSpec = tween( 101 | durationMillis = 500 102 | ) 103 | ) 104 | } 105 | viewModel.onEvent(AddEditNoteEvent.changeColor(colorInt)) 106 | } 107 | ) 108 | } 109 | } 110 | Spacer(modifier = Modifier.height(16.dp)) 111 | TransparentHintTextField(text = 112 | titleState.text, hint =titleState.hint, 113 | onValueChange = { 114 | viewModel.onEvent(AddEditNoteEvent.EnteredTitle(it)) 115 | }, onFocusChange = { 116 | viewModel.onEvent(AddEditNoteEvent.ChangeTitleFocus(it)) 117 | }, 118 | isHintVisible = titleState.isHintVisible, 119 | singleLine = true, 120 | textStyle = MaterialTheme.typography.h5) 121 | Spacer(modifier = Modifier.height(16.dp)) 122 | TransparentHintTextField(text = 123 | contentState.text, hint =contentState.hint, 124 | onValueChange = { 125 | viewModel.onEvent(AddEditNoteEvent.EnteredContent(it)) 126 | }, onFocusChange = { 127 | viewModel.onEvent(AddEditNoteEvent.ChangeContentFocus(it)) 128 | }, 129 | isHintVisible = titleState.isHintVisible, 130 | singleLine = true, 131 | textStyle = MaterialTheme.typography.body1, 132 | modifier = Modifier.fillMaxHeight() 133 | ) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/add_edit_notes/AddEditNoteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.add_edit_notes 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.example.notesapp.feature_note.domain.model.InvalidNoteException 10 | import com.example.notesapp.feature_note.domain.model.Note 11 | import com.example.notesapp.feature_note.domain.use_case.NoteUseCases 12 | import dagger.hilt.android.lifecycle.HiltViewModel 13 | import kotlinx.coroutines.flow.MutableSharedFlow 14 | import kotlinx.coroutines.flow.asSharedFlow 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | 19 | @HiltViewModel 20 | class AddEditNoteViewModel @Inject constructor( 21 | private val noteUseCases: NoteUseCases, 22 | savedStateHandle: SavedStateHandle 23 | ) : ViewModel() { 24 | 25 | private val _noteTitle = mutableStateOf(NoteTextFieldState( 26 | hint = "Enter title..." 27 | )) 28 | val noteTitle: State = _noteTitle 29 | 30 | private val _noteContent = mutableStateOf(NoteTextFieldState( 31 | hint = "Enter some content" 32 | )) 33 | val noteContent: State = _noteContent 34 | 35 | private val _noteColor = mutableStateOf(Note.noteColors.random().toArgb()) 36 | val noteColor: State = _noteColor 37 | 38 | private val _eventFlow = MutableSharedFlow() 39 | val eventFlow = _eventFlow.asSharedFlow() 40 | 41 | private var currentNoteId: Int? = null 42 | 43 | init { 44 | savedStateHandle.get("noteId")?.let { noteId -> 45 | if(noteId != -1) { 46 | viewModelScope.launch { 47 | noteUseCases.getNote(noteId)?.also { note -> 48 | currentNoteId = note.id 49 | _noteTitle.value = noteTitle.value.copy( 50 | text = note.title, 51 | isHintVisible = false 52 | ) 53 | _noteContent.value = _noteContent.value.copy( 54 | text = note.content, 55 | isHintVisible = false 56 | ) 57 | _noteColor.value = note.color 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | fun onEvent(event: AddEditNoteEvent) { 65 | when(event) { 66 | is AddEditNoteEvent.EnteredTitle -> { 67 | _noteTitle.value = noteTitle.value.copy( 68 | text = event.value 69 | ) 70 | } 71 | is AddEditNoteEvent.ChangeTitleFocus -> { 72 | _noteTitle.value = noteTitle.value.copy( 73 | isHintVisible = !event.focus.isFocused && 74 | noteTitle.value.text.isBlank() 75 | ) 76 | } 77 | is AddEditNoteEvent.EnteredContent -> { 78 | _noteContent.value = _noteContent.value.copy( 79 | text = event.value 80 | ) 81 | } 82 | is AddEditNoteEvent.ChangeContentFocus -> { 83 | _noteContent.value = _noteContent.value.copy( 84 | isHintVisible = !event.focusState.isFocused && 85 | _noteContent.value.text.isBlank() 86 | ) 87 | } 88 | is AddEditNoteEvent.changeColor -> { 89 | _noteColor.value = event.color 90 | } 91 | is AddEditNoteEvent.saveNote -> { 92 | viewModelScope.launch { 93 | try { 94 | noteUseCases.addNote( 95 | Note( 96 | title = noteTitle.value.text, 97 | content = noteContent.value.text, 98 | timesStamp = System.currentTimeMillis().toString(), 99 | color = noteColor.value, 100 | id = currentNoteId 101 | ) 102 | ) 103 | _eventFlow.emit(UiEvent.SaveNote) 104 | } catch(e: InvalidNoteException) { 105 | _eventFlow.emit( 106 | UiEvent.ShowSnackbar( 107 | message = e.message ?: "Couldn't save note" 108 | ) 109 | ) 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | sealed class UiEvent { 117 | data class ShowSnackbar(val message: String): UiEvent() 118 | object SaveNote: UiEvent() 119 | } 120 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/add_edit_notes/NoteTextFieldState.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.add_edit_notes 2 | 3 | 4 | 5 | data class NoteTextFieldState( 6 | val text:String = "", 7 | val hint:String = "", 8 | val isHintVisible:Boolean = true 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/add_edit_notes/components/TransParentHintTextField.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.add_edit_notes.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.text.BasicTextField 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.focus.FocusState 10 | import androidx.compose.ui.focus.onFocusChanged 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.text.TextStyle 13 | 14 | 15 | @Composable 16 | fun TransparentHintTextField( 17 | text:String, 18 | hint:String, 19 | modifier:Modifier = Modifier, 20 | isHintVisible:Boolean = true, 21 | onValueChange:(String) -> Unit, 22 | textStyle: TextStyle = TextStyle(), 23 | singleLine:Boolean = false, 24 | onFocusChange:(FocusState) -> Unit 25 | ){ 26 | Box( 27 | modifier = modifier 28 | ){ 29 | BasicTextField( 30 | value = text, 31 | onValueChange = onValueChange, 32 | singleLine = singleLine, 33 | textStyle = textStyle, 34 | modifier = Modifier 35 | .fillMaxWidth() 36 | .onFocusChanged { 37 | onFocusChange(it) 38 | } 39 | ) 40 | if(isHintVisible){ 41 | Text(text = hint, style = textStyle, color = Color.DarkGray) 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/notes/NoteScreen.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.notes 2 | 3 | import androidx.compose.animation.* 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.foundation.lazy.items 8 | import androidx.compose.material.* 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.Add 11 | import androidx.compose.material.icons.filled.Sort 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.rememberCoroutineScope 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.unit.dp 17 | import androidx.hilt.navigation.compose.hiltViewModel 18 | import androidx.navigation.NavController 19 | import com.example.notesapp.feature_note.presentation.notes.components.NoteItem 20 | import com.example.notesapp.feature_note.presentation.notes.components.OrderSection 21 | import com.example.notesapp.feature_note.presentation.util.Screen 22 | import kotlinx.coroutines.launch 23 | 24 | 25 | @ExperimentalAnimationApi 26 | @Composable 27 | fun NotesScreen( 28 | navController: NavController, 29 | viewModel: NotesViewModel = hiltViewModel() 30 | ) { 31 | val state = viewModel.state.value 32 | val scaffoldState = rememberScaffoldState() 33 | val scope = rememberCoroutineScope() 34 | 35 | Scaffold( 36 | floatingActionButton = { 37 | FloatingActionButton( 38 | onClick = { 39 | navController.navigate(Screen.AddEditNoteScreen.route) 40 | }, 41 | backgroundColor = MaterialTheme.colors.primary 42 | ) { 43 | Icon(imageVector = Icons.Default.Add, contentDescription = "Add note") 44 | } 45 | }, 46 | scaffoldState = scaffoldState 47 | ) { 48 | Column( 49 | modifier = Modifier 50 | .fillMaxSize() 51 | .padding(16.dp) 52 | ) { 53 | Row( 54 | modifier = Modifier.fillMaxWidth(), 55 | horizontalArrangement = Arrangement.SpaceBetween, 56 | verticalAlignment = Alignment.CenterVertically 57 | ) { 58 | Text( 59 | text = "Your note", 60 | style = MaterialTheme.typography.h4 61 | ) 62 | IconButton( 63 | onClick = { 64 | viewModel.onEvent(NotesEvent.ToggleOrderSection) 65 | }, 66 | ) { 67 | Icon( 68 | imageVector = Icons.Default.Sort, 69 | contentDescription = "Sort" 70 | ) 71 | } 72 | } 73 | AnimatedVisibility( 74 | visible = state.isOrderSectionVisible, 75 | enter = fadeIn() + slideInVertically(), 76 | exit = fadeOut() + slideOutVertically() 77 | ) { 78 | OrderSection( 79 | modifier = Modifier 80 | .fillMaxWidth() 81 | .padding(vertical = 16.dp), 82 | noteOrder = state.noteOrder, 83 | onOrderChange = { 84 | viewModel.onEvent(NotesEvent.Order(it)) 85 | } 86 | ) 87 | } 88 | Spacer(modifier = Modifier.height(16.dp)) 89 | LazyColumn(modifier = Modifier.fillMaxSize()) { 90 | items(state.notes) { note -> 91 | NoteItem( 92 | note = note, 93 | modifier = Modifier 94 | .fillMaxWidth() 95 | .clickable { 96 | navController.navigate( 97 | Screen.AddEditNoteScreen.route + 98 | "?noteId=${note.id}¬eColor=${note.color}" 99 | ) 100 | }, 101 | onDeleteClick = { 102 | viewModel.onEvent(NotesEvent.DeleteNote(note)) 103 | scope.launch { 104 | val result = scaffoldState.snackbarHostState.showSnackbar( 105 | message = "Note deleted", 106 | actionLabel = "Undo" 107 | ) 108 | if(result == SnackbarResult.ActionPerformed) { 109 | viewModel.onEvent(NotesEvent.RestoreNote) 110 | } 111 | } 112 | } 113 | ) 114 | Spacer(modifier = Modifier.height(16.dp)) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/notes/NotesEvent.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.notes 2 | 3 | import com.example.notesapp.feature_note.domain.model.Note 4 | import com.example.notesapp.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 | object RestoreNote : NotesEvent() 10 | object ToggleOrderSection : NotesEvent() 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/notes/NotesState.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.notes 2 | 3 | import com.example.notesapp.feature_note.domain.model.Note 4 | import com.example.notesapp.feature_note.domain.util.NoteOrder 5 | import com.example.notesapp.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/example/notesapp/feature_note/presentation/notes/NotesViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.notes 2 | 3 | import androidx.compose.animation.expandVertically 4 | import androidx.compose.runtime.State 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.example.notesapp.feature_note.domain.model.Note 9 | import com.example.notesapp.feature_note.domain.use_case.NoteUseCases 10 | import com.example.notesapp.feature_note.domain.util.NoteOrder 11 | import com.example.notesapp.feature_note.domain.util.OrderType 12 | import dagger.hilt.android.lifecycle.HiltViewModel 13 | import kotlinx.coroutines.Job 14 | import kotlinx.coroutines.flow.launchIn 15 | import kotlinx.coroutines.flow.onEach 16 | import kotlinx.coroutines.launch 17 | import javax.inject.Inject 18 | 19 | 20 | @HiltViewModel 21 | class NotesViewModel @Inject constructor( 22 | private val noteUseCases: NoteUseCases 23 | ) : ViewModel(){ 24 | 25 | private val _state = mutableStateOf(NotesState()) 26 | val state:State = _state 27 | private var recentDeleteNotes : Note? = null 28 | private var getNotesJob: Job? = null 29 | 30 | init { 31 | getNotes(NoteOrder.Date(OrderType.Descending)) 32 | } 33 | fun onEvent(event: NotesEvent){ 34 | when(event){ 35 | is NotesEvent.Order->{ 36 | if(state.value.noteOrder::class == event.noteOrder::class && 37 | state.value.noteOrder.orderType == event.noteOrder.orderType){ 38 | return 39 | } 40 | getNotes(event.noteOrder) 41 | } 42 | 43 | is NotesEvent.DeleteNote->{ 44 | viewModelScope.launch { 45 | noteUseCases.deleteNotes(event.note) 46 | recentDeleteNotes = event.note 47 | } 48 | } 49 | 50 | is NotesEvent.RestoreNote->{ 51 | viewModelScope.launch { 52 | noteUseCases.addNote(recentDeleteNotes ?: return@launch) 53 | recentDeleteNotes = null 54 | } 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(notesOrder: NoteOrder){ 66 | getNotesJob?.cancel() 67 | getNotesJob = noteUseCases.getNotes(notesOrder).onEach {notes-> 68 | _state.value = state.value.copy( 69 | notes = notes, 70 | noteOrder = notesOrder 71 | ) 72 | }.launchIn(viewModelScope) 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/notes/components/DefaultRadioButton.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.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.foundation.layout.wrapContentSize 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.RadioButton 9 | import androidx.compose.material.RadioButtonDefaults 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.unit.dp 15 | 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 | ) 36 | Spacer(modifier = Modifier.width(8.dp)) 37 | Text(text = text,style = MaterialTheme.typography.body1) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/notes/components/NoteItem.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.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.geometry.Size 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.graphics.Path 19 | import androidx.compose.ui.graphics.drawscope.clipPath 20 | import androidx.compose.ui.graphics.drawscope.clipRect 21 | import androidx.compose.ui.text.style.TextOverflow 22 | import androidx.compose.ui.unit.Dp 23 | import androidx.compose.ui.unit.dp 24 | import androidx.core.graphics.ColorUtils 25 | import com.example.notesapp.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 | cutCornerRadius: Dp = 30.dp, 34 | onDeleteClick: () -> Unit 35 | ){ 36 | Box( 37 | modifier = Modifier 38 | ){ 39 | Canvas(modifier = Modifier.matchParentSize()){ 40 | val clipPath = Path().apply { 41 | lineTo(size.width - cutCornerRadius.toPx(),0f) 42 | lineTo(size.width,cutCornerRadius.toPx()) 43 | lineTo(size.width,size.height) 44 | lineTo(0f,size.height) 45 | close() 46 | } 47 | clipPath(clipPath){ 48 | drawRoundRect( 49 | color = Color(note.color), 50 | size = size, 51 | cornerRadius = CornerRadius(cornerRadius.toPx()) 52 | ) 53 | drawRoundRect( 54 | color = Color( 55 | ColorUtils.blendARGB(note.color,0x000000,0.2f) 56 | ), 57 | topLeft= Offset(size.width - cutCornerRadius.toPx(),-100f), 58 | size = Size(cutCornerRadius.toPx()+100f,cutCornerRadius.toPx()+100f), 59 | cornerRadius = CornerRadius(cornerRadius.toPx()) 60 | ) 61 | } 62 | } 63 | Column( 64 | modifier = Modifier 65 | .fillMaxSize() 66 | .padding(16.dp) 67 | .padding(end = 32.dp) 68 | ) { 69 | Text( 70 | text = note.title, 71 | style = MaterialTheme.typography.h6, 72 | color = MaterialTheme.colors.surface, 73 | maxLines = 1, 74 | overflow = TextOverflow.Ellipsis 75 | ) 76 | Spacer(modifier = Modifier.height(8.dp)) 77 | Text( 78 | text = note.content, 79 | style = MaterialTheme.typography.body1, 80 | color = MaterialTheme.colors.surface, 81 | maxLines = 10, 82 | overflow = TextOverflow.Ellipsis 83 | ) 84 | } 85 | IconButton( 86 | onClick = onDeleteClick, 87 | modifier = Modifier.align(Alignment.BottomEnd)) { 88 | Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete note") 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/notes/components/OrderSection.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.notes.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.unit.dp 7 | import com.example.notesapp.feature_note.domain.util.NoteOrder 8 | import com.example.notesapp.feature_note.domain.util.OrderType 9 | 10 | 11 | @Composable 12 | fun OrderSection( 13 | modifier: Modifier = Modifier, 14 | noteOrder: NoteOrder = NoteOrder.Date(OrderType.Descending), 15 | onOrderChange: (NoteOrder) -> Unit 16 | ) { 17 | Column( 18 | modifier = modifier 19 | ) { 20 | Row( 21 | modifier = Modifier.fillMaxWidth() 22 | ) { 23 | DefaultRadioButton( 24 | text = "Title", 25 | selected = noteOrder is NoteOrder.Title, 26 | onSelect = { onOrderChange(NoteOrder.Title(noteOrder.orderType)) } 27 | ) 28 | Spacer(modifier = Modifier.width(8.dp)) 29 | DefaultRadioButton( 30 | text = "Date", 31 | selected = noteOrder is NoteOrder.Date, 32 | onSelect = { onOrderChange(NoteOrder.Date(noteOrder.orderType)) } 33 | ) 34 | Spacer(modifier = Modifier.width(8.dp)) 35 | DefaultRadioButton( 36 | text = "Color", 37 | selected = noteOrder is NoteOrder.Color, 38 | onSelect = { onOrderChange(NoteOrder.Color(noteOrder.orderType)) } 39 | ) 40 | } 41 | Spacer(modifier = Modifier.height(16.dp)) 42 | Row( 43 | modifier = Modifier.fillMaxWidth() 44 | ) { 45 | DefaultRadioButton( 46 | text = "Ascending", 47 | selected = noteOrder.orderType is OrderType.Ascending, 48 | onSelect = { 49 | onOrderChange(noteOrder.copy(OrderType.Ascending)) 50 | } 51 | ) 52 | Spacer(modifier = Modifier.width(8.dp)) 53 | DefaultRadioButton( 54 | text = "Descending", 55 | selected = noteOrder.orderType is OrderType.Descending, 56 | onSelect = { 57 | onOrderChange(noteOrder.copy(OrderType.Descending)) 58 | } 59 | ) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/feature_note/presentation/util/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.feature_note.presentation.util 2 | 3 | 4 | 5 | sealed class Screen(val route: String){ 6 | object NotesScreen: Screen("notes_screen") 7 | object AddEditNoteScreen:Screen("add_edit_note_screen") 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val DarkGray = Color(0xFF202020) 6 | val LightBlue = Color(0xFFD7E8DE) 7 | 8 | val RedOrange = Color(0xffffab91) 9 | val RedPink = Color(0xfff48fb1) 10 | val BabyBlue = Color(0xff81deea) 11 | val Violet = Color(0xffcf94da) 12 | val LightGreen = Color(0xffe7ed9b) -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.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/example/notesapp/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.ui.theme 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.darkColors 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Color 7 | 8 | private val DarkColorPalette = darkColors( 9 | primary = Color.White, 10 | background = DarkGray, 11 | onBackground = Color.White, 12 | surface = LightBlue, 13 | onSurface = DarkGray 14 | ) 15 | 16 | @Composable 17 | fun CleanArchitectureNoteAppTheme(darkTheme: Boolean = true, content: @Composable() () -> Unit) { 18 | MaterialTheme( 19 | colors = DarkColorPalette, 20 | typography = Typography, 21 | shapes = Shapes, 22 | content = content 23 | ) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/notesapp/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.example.notesapp.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /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/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /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.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ELTEGANI/NotesApp/fa06931f212f24bbc901c92cf3ea844a8d9f3fdb/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NotesApp 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 |