├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── kotlinc.xml ├── vcs.xml ├── AndroidProjectSystem.xml ├── dictionaries │ ├── raheem.xml │ └── User.xml ├── deploymentTargetDropDown.xml ├── migrations.xml ├── gradle.xml ├── runConfigurations.xml ├── jarRepositories.xml ├── misc.xml └── inspectionProfiles │ └── Project_Default.xml ├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── resources.properties │ │ ├── font │ │ │ ├── muli.ttf │ │ │ ├── muli_black.ttf │ │ │ ├── muli_bold.ttf │ │ │ └── muli_medium.ttf │ │ ├── drawable │ │ │ ├── icon.png │ │ │ ├── gravity.png │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_delete.png │ │ │ ├── ic_email.png │ │ │ ├── ic_github.png │ │ │ ├── ic_share.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ ├── splash_icon.png │ │ │ ├── splash_gravity.png │ │ │ ├── ic_customer_service.png │ │ │ ├── ic_shortcut_add_todo.png │ │ │ ├── sticker_customer_service.png │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-hdpi │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_delete.png │ │ │ ├── ic_email.png │ │ │ ├── ic_github.png │ │ │ ├── ic_share.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ └── ic_customer_service.png │ │ ├── drawable-ldpi │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_delete.png │ │ │ ├── ic_email.png │ │ │ ├── ic_github.png │ │ │ ├── ic_share.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ └── ic_customer_service.png │ │ ├── drawable-mdpi │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_delete.png │ │ │ ├── ic_email.png │ │ │ ├── ic_github.png │ │ │ ├── ic_share.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ └── ic_customer_service.png │ │ ├── drawable-night │ │ │ └── gravity.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_email.png │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_share.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_delete.png │ │ │ ├── ic_github.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ └── ic_customer_service.png │ │ ├── drawable-xxhdpi │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_delete.png │ │ │ ├── ic_email.png │ │ │ ├── ic_github.png │ │ │ ├── ic_share.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ └── ic_customer_service.png │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── drawable-xxxhdpi │ │ │ ├── ic_delete.png │ │ │ ├── ic_email.png │ │ │ ├── ic_github.png │ │ │ ├── ic_hide.png │ │ │ ├── ic_info.png │ │ │ ├── ic_share.png │ │ │ ├── ic_star.png │ │ │ ├── ic_task.png │ │ │ ├── ic_warn.png │ │ │ ├── ic_language.png │ │ │ ├── ic_telegram.png │ │ │ └── ic_customer_service.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── themes.xml │ │ │ └── strings.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values-v31 │ │ │ └── themes.xml │ │ ├── xml │ │ │ └── shortcuts.xml │ │ ├── values-en │ │ │ └── strings.xml │ │ ├── values-ru │ │ │ └── strings.xml │ │ └── values-uz │ │ │ └── strings.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── xyz │ │ │ └── teamgravity │ │ │ └── todo │ │ │ ├── injection │ │ │ ├── name │ │ │ │ ├── ApplicationScope.kt │ │ │ │ └── FullTimeFormatter.kt │ │ │ ├── bind │ │ │ │ └── ApplicationModule.kt │ │ │ ├── app │ │ │ │ └── App.kt │ │ │ └── provide │ │ │ │ ├── ViewModelModule.kt │ │ │ │ └── ApplicationModule.kt │ │ │ ├── core │ │ │ ├── constant │ │ │ │ ├── PagingConst.kt │ │ │ │ ├── TodoSort.kt │ │ │ │ ├── Shortcuts.kt │ │ │ │ └── ConnectionConst.kt │ │ │ └── util │ │ │ │ └── Helper.kt │ │ │ ├── presentation │ │ │ ├── navigation │ │ │ │ ├── MainNavGraph.kt │ │ │ │ └── Navigation.kt │ │ │ ├── component │ │ │ │ ├── topbar │ │ │ │ │ ├── TopBar.kt │ │ │ │ │ ├── TopBarSearch.kt │ │ │ │ │ ├── TopBarSortMenu.kt │ │ │ │ │ └── TopBarMoreMenu.kt │ │ │ │ ├── misc │ │ │ │ │ └── TodoConfigure.kt │ │ │ │ ├── textfield │ │ │ │ │ └── TodoTextField.kt │ │ │ │ ├── checkbox │ │ │ │ │ └── TodoImportantCheckbox.kt │ │ │ │ ├── button │ │ │ │ │ └── TodoFloatingActionButton.kt │ │ │ │ ├── dialog │ │ │ │ │ └── TodoAlertDialog.kt │ │ │ │ └── card │ │ │ │ │ ├── CardConnection.kt │ │ │ │ │ └── CardTodo.kt │ │ │ ├── activity │ │ │ │ └── Main.kt │ │ │ ├── screen │ │ │ │ ├── about │ │ │ │ │ ├── AboutScreen.kt │ │ │ │ │ ├── AboutLandscapeScreen.kt │ │ │ │ │ └── AboutPortraitScreen.kt │ │ │ │ ├── support │ │ │ │ │ ├── SupportScreen.kt │ │ │ │ │ ├── SupportLandscapeScreen.kt │ │ │ │ │ └── SupportPortraitScreen.kt │ │ │ │ └── todo │ │ │ │ │ ├── add │ │ │ │ │ ├── TodoAddViewModel.kt │ │ │ │ │ └── TodoAddScreen.kt │ │ │ │ │ └── edit │ │ │ │ │ ├── TodoEditViewModel.kt │ │ │ │ │ └── TodoEditScreen.kt │ │ │ └── theme │ │ │ │ ├── Type.kt │ │ │ │ ├── Color.kt │ │ │ │ └── Theme.kt │ │ │ └── data │ │ │ ├── model │ │ │ └── TodoModel.kt │ │ │ ├── local │ │ │ ├── todo │ │ │ │ ├── constant │ │ │ │ │ └── TodoDatabaseConst.kt │ │ │ │ ├── converter │ │ │ │ │ └── TodoConverter.kt │ │ │ │ ├── entity │ │ │ │ │ └── TodoEntity.kt │ │ │ │ ├── database │ │ │ │ │ └── TodoDatabase.kt │ │ │ │ ├── callback │ │ │ │ │ └── TodoCallback.kt │ │ │ │ └── dao │ │ │ │ │ └── TodoDao.kt │ │ │ └── preferences │ │ │ │ ├── AppPreferencesKey.kt │ │ │ │ └── AppPreferences.kt │ │ │ ├── mapper │ │ │ └── TodoMapper.kt │ │ │ └── repository │ │ │ └── TodoRepository.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro ├── schemas │ ├── xyz.teamgravity.todo.data.local.TodoDatabase │ │ └── 1.json │ ├── xyz.teamgravity.todo.viewmodel.room.MyDatabase │ │ └── 1.json │ ├── xyz.teamgravity.todo.data.local.database.TodoDatabase │ │ └── 1.json │ └── xyz.teamgravity.todo.data.local.todo.database.TodoDatabase │ │ └── 1.json └── build.gradle.kts ├── preview ├── ToDo.jpg ├── ToDo 1.0.jpg ├── ToDo 1.1.1.jpg ├── ToDo 1.1.jpg ├── summary_1.png ├── summary_2.png └── mvvm-pattern.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitignore ├── settings.gradle.kts ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | ToDo -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | google-services.json -------------------------------------------------------------------------------- /app/src/main/res/resources.properties: -------------------------------------------------------------------------------- 1 | unqualifiedResLocale=en -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /preview/ToDo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/ToDo.jpg -------------------------------------------------------------------------------- /preview/ToDo 1.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/ToDo 1.0.jpg -------------------------------------------------------------------------------- /preview/ToDo 1.1.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/ToDo 1.1.1.jpg -------------------------------------------------------------------------------- /preview/ToDo 1.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/ToDo 1.1.jpg -------------------------------------------------------------------------------- /preview/summary_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/summary_1.png -------------------------------------------------------------------------------- /preview/summary_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/summary_2.png -------------------------------------------------------------------------------- /preview/mvvm-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/preview/mvvm-pattern.png -------------------------------------------------------------------------------- /app/src/main/res/font/muli.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/font/muli.ttf -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/gravity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/gravity.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/font/muli_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/font/muli_black.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/muli_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/font/muli_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/muli_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/font/muli_medium.ttf -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/splash_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/gravity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-night/gravity.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_gravity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/splash_gravity.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_delete.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_email.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_github.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_hide.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_share.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_task.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_warn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_warn.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_language.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_telegram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_telegram.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shortcut_add_todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/ic_shortcut_add_todo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #121212 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-hdpi/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-ldpi/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-mdpi/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sticker_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable/sticker_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xhdpi/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxhdpi/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_customer_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/drawable-xxxhdpi/ic_customer_service.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raheemadamboev/todo-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #7E5233 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/injection/name/ApplicationScope.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.injection.name 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class ApplicationScope -------------------------------------------------------------------------------- /.idea/AndroidProjectSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/injection/name/FullTimeFormatter.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.injection.name 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class FullTimeFormatter 8 | -------------------------------------------------------------------------------- /.idea/dictionaries/raheem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bo'ling 5 | samarali 6 | tartibli 7 | topbar 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 04 23:22:22 UZT 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/core/constant/PagingConst.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.core.constant 2 | 3 | object PagingConst { 4 | const val PAGE_SIZE = 70 5 | const val PREFETCH_DISTANCE = PAGE_SIZE / 2 6 | const val MAX_SIZE = PAGE_SIZE * 3 7 | const val ENABLE_PLACEHOLDERS = false 8 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.idea/dictionaries/User.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | autofill 5 | livedata 6 | mmmm 7 | muli 8 | raheem 9 | snackbar 10 | teamgravity 11 | viewmodel 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/core/constant/TodoSort.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.core.constant 2 | 3 | enum class TodoSort { 4 | Name, 5 | Date; 6 | 7 | companion object { 8 | fun fromName(name: String?): TodoSort { 9 | if (name == null) return Name 10 | return entries.firstOrNull { it.name == name } ?: Name 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.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 | /.idea/deploymentTargetSelector.xml 11 | /.idea/appInsightsSettings.xml 12 | /.idea/studiobot.xml 13 | .DS_Store 14 | /build 15 | /captures 16 | .externalNativeBuild 17 | .cxx 18 | local.properties 19 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/core/constant/Shortcuts.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.core.constant 2 | 3 | enum class Shortcuts( 4 | val id: String 5 | ) { 6 | AddTodo("add_todo"); 7 | 8 | companion object { 9 | fun fromId(id: String?): Shortcuts? { 10 | if (id == null) return null 11 | return entries.firstOrNull { it.id == id } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven("https://jitpack.io") 15 | } 16 | } 17 | 18 | rootProject.name = "ToDo" 19 | include(":app") -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/navigation/MainNavGraph.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.navigation 2 | 3 | import com.ramcosta.composedestinations.animations.defaults.DefaultFadingTransitions 4 | import com.ramcosta.composedestinations.annotation.NavHostGraph 5 | 6 | @NavHostGraph( 7 | defaultTransitions = DefaultFadingTransitions::class 8 | ) 9 | annotation class MainNavGraph( 10 | val start: Boolean = false 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/model/TodoModel.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | import java.time.LocalDateTime 6 | 7 | @Parcelize 8 | data class TodoModel( 9 | val id: Long = 0, 10 | val name: String = "", 11 | val important: Boolean = false, 12 | val completed: Boolean = false, 13 | val timestamp: LocalDateTime = LocalDateTime.now() 14 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/injection/bind/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.injection.bind 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import timber.log.Timber 8 | import javax.inject.Singleton 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | abstract class ApplicationModule { 13 | 14 | @Binds 15 | @Singleton 16 | abstract fun bindTimberTree(timberDebugTree: Timber.DebugTree): Timber.Tree 17 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/core/constant/ConnectionConst.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.core.constant 2 | 3 | object ConnectionConst { 4 | 5 | /** 6 | * Support telegram link. 7 | */ 8 | const val SUPPORT_TELEGRAM = "https://t.me/raheem_adamboev" 9 | 10 | /** 11 | * Support mail. 12 | */ 13 | const val SUPPORT_MAIL = "raheemideadev@gmail.com" 14 | 15 | /** 16 | * Url of source code on Github. 17 | */ 18 | const val GITHUB_SOURCE_CODE = "https://github.com/raheemadamboev/todo-app" 19 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/todo/constant/TodoDatabaseConst.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.todo.constant 2 | 3 | object TodoDatabaseConst { 4 | 5 | /** 6 | * Database name. 7 | */ 8 | const val NAME = "TodoListDatabase" 9 | 10 | /** 11 | * Database version. 12 | */ 13 | const val VERSION = 1 14 | 15 | /** 16 | * Table ToDo. 17 | * Entity -> [xyz.teamgravity.todo.data.local.todo.entity.TodoEntity] 18 | * Model -> [xyz.teamgravity.todo.data.model.TodoModel] 19 | */ 20 | const val TABLE_TODO = "task_table" 21 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/todo/converter/TodoConverter.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.todo.converter 2 | 3 | import androidx.room.TypeConverter 4 | import xyz.teamgravity.coresdkandroid.time.TimeUtil 5 | import java.time.LocalDateTime 6 | 7 | class TodoConverter { 8 | 9 | @TypeConverter 10 | fun localDateTimeToLong(value: LocalDateTime): Long { 11 | return TimeUtil.fromLocalDateTimeToLong(value) 12 | } 13 | 14 | @TypeConverter 15 | fun longToLocalDateTime(value: Long): LocalDateTime { 16 | return TimeUtil.fromLongToLocalDateTime(value) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/injection/app/App.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.injection.app 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | import timber.log.Timber 6 | import xyz.teamgravity.coresdkandroid.review.ReviewManager 7 | import xyz.teamgravity.todo.BuildConfig 8 | import javax.inject.Inject 9 | 10 | @HiltAndroidApp 11 | class App : Application() { 12 | 13 | @Inject 14 | lateinit var tree: Timber.Tree 15 | 16 | @Inject 17 | lateinit var review: ReviewManager 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | if (BuildConfig.DEBUG) Timber.plant(tree) 23 | review.monitor() 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/todo/entity/TodoEntity.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.todo.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import xyz.teamgravity.todo.data.local.todo.constant.TodoDatabaseConst 7 | import java.time.LocalDateTime 8 | 9 | @Entity(TodoDatabaseConst.TABLE_TODO) 10 | data class TodoEntity( 11 | @PrimaryKey(autoGenerate = true) @ColumnInfo("_id") val id: Long = 0, 12 | @ColumnInfo("name") val name: String, 13 | @ColumnInfo("important") val important: Boolean, 14 | @ColumnInfo("completed") val completed: Boolean, 15 | @ColumnInfo("timestamp") val timestamp: LocalDateTime 16 | ) -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/preferences/AppPreferencesKey.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.preferences 2 | 3 | import xyz.teamgravity.coresdkandroid.preferences.PreferencesKey 4 | import xyz.teamgravity.todo.core.constant.TodoSort 5 | 6 | enum class AppPreferencesKey( 7 | override val key: String, 8 | override val default: Any?, 9 | override val encrypted: Boolean 10 | ) : PreferencesKey { 11 | Sorting( 12 | key = "xyz.teamgravity.todo.Sorting", 13 | default = TodoSort.Date.name, 14 | encrypted = false 15 | ), 16 | HideCompleted( 17 | key = "xyz.teamgravity.todo.HideCompleted", 18 | default = false, 19 | encrypted = false 20 | ); 21 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/injection/provide/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.injection.provide 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.ViewModelComponent 7 | import dagger.hilt.android.scopes.ViewModelScoped 8 | import xyz.teamgravity.todo.injection.name.FullTimeFormatter 9 | import java.time.format.DateTimeFormatter 10 | import java.util.Locale 11 | 12 | @Module 13 | @InstallIn(ViewModelComponent::class) 14 | object ViewModelModule { 15 | 16 | @Provides 17 | @ViewModelScoped 18 | @FullTimeFormatter 19 | fun provideFullTimeFormatter(): DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy, MMMM d, HH:mm", Locale.getDefault()) 20 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/todo/database/TodoDatabase.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.todo.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import xyz.teamgravity.todo.data.local.todo.constant.TodoDatabaseConst 7 | import xyz.teamgravity.todo.data.local.todo.converter.TodoConverter 8 | import xyz.teamgravity.todo.data.local.todo.dao.TodoDao 9 | import xyz.teamgravity.todo.data.local.todo.entity.TodoEntity 10 | 11 | @TypeConverters(TodoConverter::class) 12 | @Database( 13 | entities = [TodoEntity::class], 14 | version = TodoDatabaseConst.VERSION 15 | ) 16 | abstract class TodoDatabase : RoomDatabase() { 17 | 18 | abstract fun todoDao(): TodoDao 19 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/mapper/TodoMapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.mapper 2 | 3 | import xyz.teamgravity.todo.data.local.todo.entity.TodoEntity 4 | import xyz.teamgravity.todo.data.model.TodoModel 5 | 6 | /////////////////////////////////////////////////////////////////////////// 7 | // Model 8 | /////////////////////////////////////////////////////////////////////////// 9 | 10 | fun TodoEntity.toModel(): TodoModel { 11 | return TodoModel( 12 | id = id, 13 | name = name, 14 | important = important, 15 | completed = completed, 16 | timestamp = timestamp 17 | ) 18 | } 19 | 20 | /////////////////////////////////////////////////////////////////////////// 21 | // Entity 22 | /////////////////////////////////////////////////////////////////////////// 23 | 24 | fun TodoModel.toEntity(): TodoEntity { 25 | return TodoEntity( 26 | id = id, 27 | name = name, 28 | important = important, 29 | completed = completed, 30 | timestamp = timestamp 31 | ) 32 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/topbar/TopBar.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.topbar 2 | 3 | import androidx.compose.foundation.layout.RowScope 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.TopAppBar 6 | import androidx.compose.material3.TopAppBarDefaults 7 | import androidx.compose.runtime.Composable 8 | 9 | @Composable 10 | fun TopBar( 11 | title: @Composable () -> Unit, 12 | navigationIcon: @Composable () -> Unit = {}, 13 | actions: @Composable RowScope.() -> Unit = {}, 14 | ) { 15 | TopAppBar( 16 | title = title, 17 | navigationIcon = navigationIcon, 18 | actions = actions, 19 | colors = TopAppBarDefaults.topAppBarColors( 20 | containerColor = MaterialTheme.colorScheme.primary, 21 | titleContentColor = MaterialTheme.colorScheme.onPrimary, 22 | actionIconContentColor = MaterialTheme.colorScheme.onPrimary, 23 | navigationIconContentColor = MaterialTheme.colorScheme.onPrimary 24 | ) 25 | ) 26 | } -------------------------------------------------------------------------------- /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.kts. 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 22 | 23 | -keep class xyz.teamgravity.todo.data.model.** { *; } 24 | -keep class xyz.teamgravity.todo.data.local.todo.entity.** { *; } 25 | -keep class xyz.teamgravity.coresdkandroid.** { *; } 26 | -keep class xyz.teamgravity.coresdkcompose.** { *; } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/activity/Main.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.activity 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 8 | import dagger.hilt.android.AndroidEntryPoint 9 | import xyz.teamgravity.todo.presentation.navigation.Navigation 10 | import xyz.teamgravity.todo.presentation.theme.TodoTheme 11 | 12 | @AndroidEntryPoint 13 | class Main : ComponentActivity() { 14 | 15 | companion object { 16 | const val EXTRA_SHORTCUT_ID = "Main_extraShortcutId" 17 | } 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | installSplashScreen() 21 | super.onCreate(savedInstanceState) 22 | enableEdgeToEdge() 23 | val intent = if (savedInstanceState == null) intent else null 24 | setContent { 25 | TodoTheme { 26 | Navigation(intent) 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/about/AboutScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.about 2 | 3 | import android.content.res.Configuration 4 | import androidx.activity.compose.LocalOnBackPressedDispatcherOwner 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.platform.LocalConfiguration 8 | import com.ramcosta.composedestinations.annotation.Destination 9 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator 10 | import xyz.teamgravity.todo.presentation.navigation.MainNavGraph 11 | 12 | @Destination 13 | @Composable 14 | fun AboutScreen( 15 | navigator: DestinationsNavigator 16 | ) { 17 | val configuration = LocalConfiguration.current 18 | val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher 19 | val onBackButtonClick: () -> Unit = remember { { dispatcher?.onBackPressed() ?: navigator.navigateUp() } } 20 | 21 | when (configuration.orientation) { 22 | Configuration.ORIENTATION_LANDSCAPE -> AboutLandscapeScreen(onBackButtonClick = onBackButtonClick) 23 | else -> AboutPortraitScreen(onBackButtonClick = onBackButtonClick) 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/support/SupportScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.support 2 | 3 | import android.content.res.Configuration 4 | import androidx.activity.compose.LocalOnBackPressedDispatcherOwner 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.platform.LocalConfiguration 8 | import com.ramcosta.composedestinations.annotation.Destination 9 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator 10 | import xyz.teamgravity.todo.presentation.navigation.MainNavGraph 11 | 12 | @Destination 13 | @Composable 14 | fun SupportScreen( 15 | navigator: DestinationsNavigator 16 | ) { 17 | val configuration = LocalConfiguration.current 18 | val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher 19 | val onBackButtonClick: () -> Unit = remember { { dispatcher?.onBackPressed() ?: navigator.navigateUp() } } 20 | 21 | when (configuration.orientation) { 22 | Configuration.ORIENTATION_LANDSCAPE -> SupportLandscapeScreen(onBackButtonClick = onBackButtonClick) 23 | else -> SupportPortraitScreen(onBackButtonClick = onBackButtonClick) 24 | } 25 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/misc/TodoConfigure.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.misc 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import xyz.teamgravity.todo.presentation.component.checkbox.TodoImportantCheckbox 11 | import xyz.teamgravity.todo.presentation.component.textfield.TodoTextField 12 | 13 | @Composable 14 | fun TodoConfigure( 15 | name: String, 16 | onNameChange: (name: String) -> Unit, 17 | important: Boolean, 18 | onImportantChange: (important: Boolean) -> Unit 19 | ) { 20 | Column( 21 | modifier = Modifier.fillMaxWidth() 22 | ) { 23 | TodoTextField( 24 | value = name, 25 | onValueChange = onNameChange 26 | ) 27 | Spacer( 28 | modifier = Modifier.height(8.dp) 29 | ) 30 | TodoImportantCheckbox( 31 | important = important, 32 | onImportantChange = onImportantChange 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/todo/callback/TodoCallback.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.todo.callback 2 | 3 | import android.app.Application 4 | import androidx.room.RoomDatabase 5 | import androidx.sqlite.db.SupportSQLiteDatabase 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.launch 8 | import xyz.teamgravity.todo.R 9 | import xyz.teamgravity.todo.data.local.todo.database.TodoDatabase 10 | import xyz.teamgravity.todo.data.local.todo.entity.TodoEntity 11 | import java.time.LocalDateTime 12 | import javax.inject.Provider 13 | 14 | class TodoCallback( 15 | private val application: Application, 16 | private val db: Provider, 17 | private val scope: CoroutineScope 18 | ) : RoomDatabase.Callback() { 19 | 20 | override fun onCreate(db: SupportSQLiteDatabase) { 21 | super.onCreate(db) 22 | scope.launch { 23 | this@TodoCallback.db.get().todoDao().insertTodo( 24 | TodoEntity( 25 | name = application.getString(R.string.predefined_todo_name), 26 | important = true, 27 | completed = false, 28 | timestamp = LocalDateTime.now() 29 | ) 30 | ) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/textfield/TodoTextField.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.textfield 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.TextField 6 | import androidx.compose.material3.TextFieldDefaults 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.unit.dp 11 | import xyz.teamgravity.coresdkcompose.text.TextPlain 12 | import xyz.teamgravity.todo.R 13 | 14 | @Composable 15 | fun TodoTextField( 16 | value: String, 17 | onValueChange: (value: String) -> Unit 18 | ) { 19 | TextField( 20 | value = value, 21 | onValueChange = onValueChange, 22 | placeholder = { 23 | TextPlain( 24 | id = R.string.task_name 25 | ) 26 | }, 27 | colors = TextFieldDefaults.colors( 28 | focusedContainerColor = Color.Transparent, 29 | unfocusedContainerColor = Color.Transparent, 30 | disabledContainerColor = Color.Transparent, 31 | errorContainerColor = Color.Transparent 32 | ), 33 | modifier = Modifier 34 | .fillMaxWidth() 35 | .padding(top = 8.dp, start = 8.dp, end = 8.dp), 36 | ) 37 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/checkbox/TodoImportantCheckbox.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.checkbox 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.selection.toggleable 7 | import androidx.compose.material3.Checkbox 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.semantics.Role 12 | import androidx.compose.ui.unit.dp 13 | import xyz.teamgravity.coresdkcompose.text.TextPlain 14 | import xyz.teamgravity.todo.R 15 | 16 | @Composable 17 | fun TodoImportantCheckbox( 18 | important: Boolean, 19 | onImportantChange: (checked: Boolean) -> Unit 20 | ) { 21 | Row( 22 | verticalAlignment = Alignment.CenterVertically, 23 | modifier = Modifier.toggleable( 24 | value = important, 25 | role = Role.Checkbox, 26 | onValueChange = onImportantChange 27 | ) 28 | ) { 29 | Checkbox( 30 | checked = important, 31 | onCheckedChange = onImportantChange 32 | ) 33 | TextPlain( 34 | id = R.string.important_task 35 | ) 36 | Spacer( 37 | modifier = Modifier.width(16.dp) 38 | ) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/navigation/Navigation.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.navigation 2 | 3 | import android.content.Intent 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.LaunchedEffect 6 | import androidx.navigation.NavHostController 7 | import com.ramcosta.composedestinations.DestinationsNavHost 8 | import com.ramcosta.composedestinations.generated.NavGraphs 9 | import com.ramcosta.composedestinations.generated.destinations.TodoAddScreenDestination 10 | import com.ramcosta.composedestinations.rememberNavHostEngine 11 | import com.ramcosta.composedestinations.spec.NavHostEngine 12 | import xyz.teamgravity.todo.core.constant.Shortcuts 13 | import xyz.teamgravity.todo.presentation.activity.Main 14 | 15 | @Composable 16 | fun Navigation( 17 | intent: Intent?, 18 | engine: NavHostEngine = rememberNavHostEngine(), 19 | controller: NavHostController = engine.rememberNavController() 20 | ) { 21 | LaunchedEffect( 22 | key1 = intent, 23 | block = { 24 | Shortcuts.fromId(intent?.getStringExtra(Main.EXTRA_SHORTCUT_ID))?.let { shortcut -> 25 | when (shortcut) { 26 | Shortcuts.AddTodo -> { 27 | controller.navigate(TodoAddScreenDestination.route) 28 | } 29 | } 30 | } 31 | } 32 | ) 33 | 34 | DestinationsNavHost( 35 | navGraph = NavGraphs.main, 36 | engine = engine, 37 | navController = controller 38 | ) 39 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/button/TodoFloatingActionButton.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.button 2 | 3 | import android.content.res.Configuration 4 | import androidx.annotation.StringRes 5 | import androidx.compose.foundation.layout.WindowInsets 6 | import androidx.compose.foundation.layout.safeDrawing 7 | import androidx.compose.foundation.layout.windowInsetsPadding 8 | import androidx.compose.material3.FloatingActionButton 9 | import androidx.compose.material3.Icon 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.vector.ImageVector 14 | import androidx.compose.ui.platform.LocalConfiguration 15 | import androidx.compose.ui.res.stringResource 16 | 17 | @Composable 18 | fun TodoFloatingActionButton( 19 | onClick: () -> Unit, 20 | icon: ImageVector, 21 | @StringRes contentDescription: Int 22 | ) { 23 | val configuration = LocalConfiguration.current 24 | FloatingActionButton( 25 | onClick = onClick, 26 | containerColor = MaterialTheme.colorScheme.primary, 27 | contentColor = MaterialTheme.colorScheme.onPrimary, 28 | modifier = if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) Modifier.windowInsetsPadding(WindowInsets.safeDrawing) else Modifier 29 | ) { 30 | Icon( 31 | imageVector = icon, 32 | contentDescription = stringResource(id = contentDescription), 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/preferences/AppPreferences.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.preferences 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.map 5 | import xyz.teamgravity.coresdkandroid.preferences.Preferences 6 | import xyz.teamgravity.todo.core.constant.TodoSort 7 | 8 | class AppPreferences( 9 | private val preferences: Preferences 10 | ) { 11 | 12 | /////////////////////////////////////////////////////////////////////////// 13 | // Upsert 14 | /////////////////////////////////////////////////////////////////////////// 15 | 16 | suspend fun upsertSorting(value: TodoSort) { 17 | preferences.upsertString( 18 | key = AppPreferencesKey.Sorting, 19 | value = value.name 20 | ) 21 | } 22 | 23 | suspend fun upsertHideCompleted(value: Boolean) { 24 | preferences.upsertBoolean( 25 | key = AppPreferencesKey.HideCompleted, 26 | value = value 27 | ) 28 | } 29 | 30 | /////////////////////////////////////////////////////////////////////////// 31 | // Get 32 | /////////////////////////////////////////////////////////////////////////// 33 | 34 | fun getSorting(): Flow { 35 | return preferences.getString(AppPreferencesKey.Sorting).map { TodoSort.fromName(it) } 36 | } 37 | 38 | fun getHideCompleted(): Flow { 39 | return preferences.getBoolean(AppPreferencesKey.HideCompleted).map { it ?: AppPreferencesKey.HideCompleted.default as Boolean } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.font.Font 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import xyz.teamgravity.todo.R 8 | 9 | private val muli: FontFamily = FontFamily( 10 | Font(R.font.muli, FontWeight.Normal), 11 | Font(R.font.muli_medium, FontWeight.Medium), 12 | Font(R.font.muli_bold, FontWeight.Bold), 13 | Font(R.font.muli_black, FontWeight.Black) 14 | ) 15 | 16 | private val default: Typography = Typography() 17 | 18 | val Typography: Typography = Typography( 19 | displayLarge = default.displayLarge.copy(fontFamily = muli), 20 | displayMedium = default.displayMedium.copy(fontFamily = muli), 21 | displaySmall = default.displaySmall.copy(fontFamily = muli), 22 | headlineLarge = default.headlineLarge.copy(fontFamily = muli), 23 | headlineMedium = default.headlineMedium.copy(fontFamily = muli), 24 | headlineSmall = default.headlineSmall.copy(fontFamily = muli), 25 | titleLarge = default.titleLarge.copy(fontFamily = muli), 26 | titleMedium = default.titleMedium.copy(fontFamily = muli), 27 | titleSmall = default.titleSmall.copy(fontFamily = muli), 28 | labelLarge = default.labelLarge.copy(fontFamily = muli), 29 | labelMedium = default.labelMedium.copy(fontFamily = muli), 30 | labelSmall = default.labelSmall.copy(fontFamily = muli), 31 | bodyLarge = default.bodyLarge.copy(fontFamily = muli), 32 | bodyMedium = default.bodyMedium.copy(fontFamily = muli), 33 | bodySmall = default.bodySmall.copy(fontFamily = muli) 34 | ) -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/dialog/TodoAlertDialog.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.dialog 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.material3.AlertDialog 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.material3.TextButton 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.res.stringResource 10 | import xyz.teamgravity.coresdkcompose.text.TextPlain 11 | import xyz.teamgravity.todo.R 12 | import xyz.teamgravity.todo.presentation.theme.backgroundDialogButton 13 | 14 | @Composable 15 | fun TodoAlertDialog( 16 | @StringRes title: Int, 17 | @StringRes message: Int, 18 | onDismiss: () -> Unit, 19 | onConfirm: () -> Unit 20 | ) { 21 | AlertDialog( 22 | onDismissRequest = onDismiss, 23 | title = { 24 | TextPlain( 25 | id = title 26 | ) 27 | }, 28 | text = { 29 | TextPlain( 30 | id = message 31 | ) 32 | }, 33 | dismissButton = { 34 | TextButton( 35 | onClick = onDismiss 36 | ) { 37 | Text( 38 | text = stringResource(id = R.string.no), 39 | color = MaterialTheme.colorScheme.backgroundDialogButton() 40 | ) 41 | } 42 | }, 43 | confirmButton = { 44 | TextButton( 45 | onClick = onConfirm 46 | ) { 47 | Text( 48 | text = stringResource(id = R.string.yes), 49 | color = MaterialTheme.colorScheme.backgroundDialogButton() 50 | ) 51 | } 52 | } 53 | ) 54 | } -------------------------------------------------------------------------------- /app/schemas/xyz.teamgravity.todo.data.local.TodoDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "57e6db94bb23992dccc6b64e2fb813f5", 6 | "entities": [ 7 | { 8 | "tableName": "task_table", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `important` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "_id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "important", 25 | "columnName": "important", 26 | "affinity": "INTEGER", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "completed", 31 | "columnName": "completed", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "timestamp", 37 | "columnName": "timestamp", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '57e6db94bb23992dccc6b64e2fb813f5')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/xyz.teamgravity.todo.viewmodel.room.MyDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "57e6db94bb23992dccc6b64e2fb813f5", 6 | "entities": [ 7 | { 8 | "tableName": "task_table", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `important` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "_id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "important", 25 | "columnName": "important", 26 | "affinity": "INTEGER", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "completed", 31 | "columnName": "completed", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "timestamp", 37 | "columnName": "timestamp", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '57e6db94bb23992dccc6b64e2fb813f5')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/xyz.teamgravity.todo.data.local.database.TodoDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "57e6db94bb23992dccc6b64e2fb813f5", 6 | "entities": [ 7 | { 8 | "tableName": "task_table", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `important` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "_id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "important", 25 | "columnName": "important", 26 | "affinity": "INTEGER", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "completed", 31 | "columnName": "completed", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "timestamp", 37 | "columnName": "timestamp", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '57e6db94bb23992dccc6b64e2fb813f5')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/xyz.teamgravity.todo.data.local.todo.database.TodoDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "57e6db94bb23992dccc6b64e2fb813f5", 6 | "entities": [ 7 | { 8 | "tableName": "task_table", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `important` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "_id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "important", 25 | "columnName": "important", 26 | "affinity": "INTEGER", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "completed", 31 | "columnName": "completed", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "timestamp", 37 | "columnName": "timestamp", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '57e6db94bb23992dccc6b64e2fb813f5')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.ColorScheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.graphics.Color 7 | 8 | val Black = Color(0xFF000000) 9 | val White = Color(0xFFFFFFFF) 10 | val White700 = Color(0xFFBBBBBB) 11 | val DarkGray = Color(0xFF121212) 12 | val DarkGray200 = Color(0xFF1A1A1A) 13 | val Brown200 = Color(0xFFB07E5D) 14 | val Brown500 = Color(0xFF7E5233) 15 | val Brown700 = Color(0xFF4F290C) 16 | 17 | val Brown10 = Color(0xFF24180F) 18 | val Brown20 = Color(0xFF482F1E) 19 | val Brown30 = Color(0xFF6D472C) 20 | val Brown40 = Color(0xFF915F3B) 21 | val Brown80 = Color(0xFFE1C8B7) 22 | val Brown90 = Color(0xFFF0E4DB) 23 | 24 | val DarkBrown10 = Color(0xFF2C1707) 25 | val DarkBrown20 = Color(0xFF592E0D) 26 | val DarkBrown30 = Color(0xFF854514) 27 | val DarkBrown40 = Color(0xFFB15C1B) 28 | val DarkBrown80 = Color(0xFFF2C7A6) 29 | val DarkBrown90 = Color(0xFFF8E3D3) 30 | 31 | val Violet10 = Color(0xFF330033) 32 | val Violet20 = Color(0xFF660066) 33 | val Violet30 = Color(0xFF990099) 34 | val Violet40 = Color(0xFFCC00CC) 35 | val Violet80 = Color(0xFFFF99FF) 36 | val Violet90 = Color(0xFFFFCCFF) 37 | 38 | val Red10 = Color(0xFF410001) 39 | val Red20 = Color(0xFF680003) 40 | val Red30 = Color(0xFF930006) 41 | val Red40 = Color(0xFFBA1B1B) 42 | val Red80 = Color(0xFFFFB4A9) 43 | val Red90 = Color(0xFFFFDAD4) 44 | 45 | val Grey10 = Color(0xFF191C1D) 46 | val Grey20 = Color(0xFF2D3132) 47 | val Grey90 = Color(0xFFE0E3E3) 48 | val Grey95 = Color(0xFFEFF1F1) 49 | val Grey99 = Color(0xFFFBFDFD) 50 | 51 | val BrownGrey30 = Color(0xFF5F493A) 52 | val BrownGrey50 = Color(0xFF9E7A61) 53 | val BrownGrey60 = Color(0xFFB19581) 54 | val BrownGrey80 = Color(0xFFD8CAC0) 55 | val BrownGrey90 = Color(0xFFECE4DF) 56 | 57 | @Composable 58 | fun ColorScheme.backgroundDialogButton(): Color { 59 | return if (isSystemInDarkTheme()) BrownGrey50 else primary 60 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/core/util/Helper.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.core.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.core.net.toUri 6 | import xyz.teamgravity.coresdkandroid.android.safelyStartActivity 7 | import xyz.teamgravity.coresdkandroid.connect.ConnectUtil 8 | import xyz.teamgravity.todo.R 9 | import xyz.teamgravity.todo.core.constant.ConnectionConst 10 | 11 | object Helper { 12 | 13 | private fun getShareAppText(context: Context): String { 14 | return context.getString(R.string.share_app, ConnectUtil.getAppPlayStorePageUrl(context)) 15 | } 16 | 17 | /////////////////////////////////////////////////////////////////////////// 18 | // API 19 | /////////////////////////////////////////////////////////////////////////// 20 | 21 | /** 22 | * Shares the app text with other apps. 23 | */ 24 | fun shareApp(context: Context) { 25 | ConnectUtil.shareText( 26 | context = context, 27 | text = getShareAppText(context), 28 | chooserTitle = context.getString(R.string.choose) 29 | ) 30 | } 31 | 32 | /** 33 | * Navigates the user to show source code on Github. 34 | */ 35 | fun viewSourceCode(context: Context) { 36 | context.safelyStartActivity(Intent(Intent.ACTION_VIEW, ConnectionConst.GITHUB_SOURCE_CODE.toUri())) 37 | } 38 | 39 | /** 40 | * Navigates the user to connect us via Telegram. 41 | */ 42 | fun connectViaTelegram(context: Context) { 43 | context.safelyStartActivity(Intent(Intent.ACTION_VIEW, ConnectionConst.SUPPORT_TELEGRAM.toUri())) 44 | } 45 | 46 | /** 47 | * Navigates the user to connect us via Email. 48 | */ 49 | fun connectViaEmail(context: Context) { 50 | ConnectUtil.connectViaEmail( 51 | context = context, 52 | email = ConnectionConst.SUPPORT_MAIL, 53 | subject = context.getString(R.string.improvement), 54 | chooserTitle = context.getString(R.string.choose) 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/topbar/TopBarSearch.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.topbar 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.OutlinedTextField 9 | import androidx.compose.material3.OutlinedTextFieldDefaults 10 | import androidx.compose.material3.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.res.stringResource 16 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 17 | import xyz.teamgravity.todo.R 18 | 19 | @Composable 20 | fun TopBarSearch( 21 | query: String, 22 | onQueryChange: (query: String) -> Unit, 23 | onCancel: () -> Unit 24 | ) { 25 | Row( 26 | verticalAlignment = Alignment.CenterVertically, 27 | modifier = Modifier.fillMaxWidth() 28 | ) { 29 | IconButtonPlain( 30 | onClick = onCancel, 31 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 32 | contentDescription = R.string.cd_back_button 33 | ) 34 | OutlinedTextField( 35 | value = query, 36 | onValueChange = onQueryChange, 37 | singleLine = true, 38 | placeholder = { 39 | Text( 40 | text = stringResource(id = R.string.search), 41 | color = MaterialTheme.colorScheme.onPrimary, 42 | style = MaterialTheme.typography.bodyLarge 43 | ) 44 | }, 45 | colors = OutlinedTextFieldDefaults.colors( 46 | unfocusedBorderColor = Color.Transparent, 47 | focusedBorderColor = Color.Transparent, 48 | cursorColor = MaterialTheme.colorScheme.onPrimary, 49 | focusedTextColor = MaterialTheme.colorScheme.onPrimary, 50 | unfocusedTextColor = MaterialTheme.colorScheme.onPrimary 51 | ), 52 | textStyle = MaterialTheme.typography.bodyLarge 53 | ) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/topbar/TopBarSortMenu.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.topbar 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.automirrored.rounded.Sort 6 | import androidx.compose.material.icons.rounded.Done 7 | import androidx.compose.material3.DropdownMenu 8 | import androidx.compose.material3.Icon 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.res.stringResource 11 | import kotlinx.collections.immutable.ImmutableList 12 | import kotlinx.collections.immutable.persistentListOf 13 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 14 | import xyz.teamgravity.coresdkcompose.menu.GDropdownMenuItem 15 | import xyz.teamgravity.todo.R 16 | import xyz.teamgravity.todo.core.constant.TodoSort 17 | 18 | @Composable 19 | fun TopBarSortMenu( 20 | expanded: Boolean, 21 | sorting: TodoSort?, 22 | onExpand: () -> Unit, 23 | onDismiss: () -> Unit, 24 | onSort: (sort: TodoSort) -> Unit, 25 | menus: ImmutableList = SORT_MENUS 26 | ) { 27 | IconButtonPlain( 28 | onClick = onExpand, 29 | icon = Icons.AutoMirrored.Rounded.Sort, 30 | contentDescription = R.string.cd_sort 31 | ) 32 | DropdownMenu( 33 | expanded = expanded, 34 | onDismissRequest = onDismiss 35 | ) { 36 | menus.forEach { menu -> 37 | GDropdownMenuItem( 38 | onDismiss = onDismiss, 39 | onClick = { 40 | onSort(menu.sort) 41 | }, 42 | label = menu.title, 43 | trailingIcon = { 44 | if (sorting == menu.sort) { 45 | Icon( 46 | imageVector = Icons.Rounded.Done, 47 | contentDescription = stringResource(R.string.cd_selected_sort, stringResource(menu.title)) 48 | ) 49 | } 50 | } 51 | ) 52 | } 53 | } 54 | } 55 | 56 | data class SortMenu( 57 | @StringRes val title: Int, 58 | val sort: TodoSort 59 | ) 60 | 61 | private val SORT_MENUS: ImmutableList = persistentListOf( 62 | SortMenu( 63 | title = R.string.sort_by_name, 64 | sort = TodoSort.Name 65 | ), 66 | SortMenu( 67 | title = R.string.sort_by_date, 68 | sort = TodoSort.Date 69 | ) 70 | ) -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/todo/add/TodoAddViewModel.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.todo.add 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.viewModelScope 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.channels.Channel 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.receiveAsFlow 13 | import kotlinx.coroutines.launch 14 | import xyz.teamgravity.todo.R 15 | import xyz.teamgravity.todo.data.model.TodoModel 16 | import xyz.teamgravity.todo.data.repository.TodoRepository 17 | import javax.inject.Inject 18 | 19 | @HiltViewModel 20 | class TodoAddViewModel @Inject constructor( 21 | private val repository: TodoRepository 22 | ) : ViewModel() { 23 | 24 | var name: String by mutableStateOf("") 25 | private set 26 | 27 | var important: Boolean by mutableStateOf(false) 28 | private set 29 | 30 | private val _event = Channel() 31 | val event: Flow = _event.receiveAsFlow() 32 | 33 | /////////////////////////////////////////////////////////////////////////// 34 | // API 35 | /////////////////////////////////////////////////////////////////////////// 36 | 37 | fun onNameChange(value: String) { 38 | name = value 39 | } 40 | 41 | fun onImportantChange(value: Boolean) { 42 | important = value 43 | } 44 | 45 | fun onSaveTodo() { 46 | viewModelScope.launch { 47 | if (name.isBlank()) { 48 | _event.send(TodoAddEvent.InvalidInput(R.string.error_name)) 49 | return@launch 50 | } 51 | 52 | repository.insertTodo( 53 | TodoModel( 54 | name = name, 55 | important = important 56 | ) 57 | ) 58 | 59 | _event.send(TodoAddEvent.TodoAdded) 60 | } 61 | } 62 | 63 | /////////////////////////////////////////////////////////////////////////// 64 | // Misc 65 | /////////////////////////////////////////////////////////////////////////// 66 | 67 | sealed interface TodoAddEvent { 68 | data class InvalidInput(@StringRes val message: Int) : TodoAddEvent 69 | data object TodoAdded : TodoAddEvent 70 | } 71 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/card/CardConnection.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.card 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.StringRes 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.size 11 | import androidx.compose.foundation.shape.CircleShape 12 | import androidx.compose.material3.ElevatedCard 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.material3.Text 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Alignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.draw.clip 20 | import androidx.compose.ui.res.painterResource 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.text.style.TextAlign 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.unit.sp 26 | import xyz.teamgravity.todo.presentation.theme.White 27 | 28 | @Composable 29 | fun CardConnection( 30 | onClick: () -> Unit, 31 | @DrawableRes icon: Int, 32 | @StringRes title: Int, 33 | @StringRes contentDescription: Int, 34 | fillMaxSize: Boolean, 35 | modifier: Modifier, 36 | ) { 37 | ElevatedCard( 38 | onClick = onClick, 39 | shape = MaterialTheme.shapes.extraLarge, 40 | modifier = modifier 41 | ) { 42 | Row( 43 | verticalAlignment = Alignment.CenterVertically, 44 | modifier = Modifier 45 | .then(if (fillMaxSize) Modifier.fillMaxSize() else Modifier) 46 | .padding(16.dp) 47 | ) { 48 | Box( 49 | contentAlignment = Alignment.Center, 50 | modifier = Modifier 51 | .size(64.dp) 52 | .clip(CircleShape) 53 | .background(MaterialTheme.colorScheme.primary) 54 | ) { 55 | Icon( 56 | painter = painterResource(id = icon), 57 | contentDescription = stringResource(id = contentDescription), 58 | tint = White, 59 | modifier = Modifier.size(32.dp) 60 | ) 61 | } 62 | Text( 63 | text = stringResource(id = title), 64 | textAlign = TextAlign.Center, 65 | fontSize = 24.sp, 66 | fontWeight = FontWeight.Bold, 67 | modifier = Modifier.weight(1F) 68 | ) 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/todo/edit/TodoEditViewModel.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.todo.edit 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import androidx.lifecycle.SavedStateHandle 8 | import androidx.lifecycle.ViewModel 9 | import androidx.lifecycle.viewModelScope 10 | import com.ramcosta.composedestinations.generated.destinations.TodoEditScreenDestination 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.channels.Channel 13 | import kotlinx.coroutines.flow.Flow 14 | import kotlinx.coroutines.flow.receiveAsFlow 15 | import kotlinx.coroutines.launch 16 | import xyz.teamgravity.todo.R 17 | import xyz.teamgravity.todo.data.repository.TodoRepository 18 | import xyz.teamgravity.todo.injection.name.FullTimeFormatter 19 | import java.time.format.DateTimeFormatter 20 | import javax.inject.Inject 21 | 22 | @HiltViewModel 23 | class TodoEditViewModel @Inject constructor( 24 | private val repository: TodoRepository, 25 | @FullTimeFormatter private val formatter: DateTimeFormatter, 26 | handle: SavedStateHandle 27 | ) : ViewModel() { 28 | 29 | private val args: TodoEditScreenArgs = TodoEditScreenDestination.argsFrom(handle) 30 | 31 | var name: String by mutableStateOf(args.todo.name) 32 | private set 33 | 34 | var important: Boolean by mutableStateOf(args.todo.important) 35 | private set 36 | 37 | val timestamp: String by mutableStateOf(formatter.format(args.todo.timestamp)) 38 | 39 | private val _event = Channel() 40 | val event: Flow = _event.receiveAsFlow() 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | // API 44 | /////////////////////////////////////////////////////////////////////////// 45 | 46 | fun onNameChange(value: String) { 47 | name = value 48 | } 49 | 50 | fun onImportantChange(value: Boolean) { 51 | important = value 52 | } 53 | 54 | fun onUpdateTodo() { 55 | viewModelScope.launch { 56 | if (name.isBlank()) { 57 | _event.send(TodoEditEvent.InvalidInput(R.string.error_name)) 58 | return@launch 59 | } 60 | 61 | repository.updateTodo( 62 | args.todo.copy( 63 | name = name, 64 | important = important 65 | ) 66 | ) 67 | 68 | _event.send(TodoEditEvent.TodoUpdated) 69 | } 70 | } 71 | 72 | /////////////////////////////////////////////////////////////////////////// 73 | // Misc 74 | /////////////////////////////////////////////////////////////////////////// 75 | 76 | sealed interface TodoEditEvent { 77 | data class InvalidInput(@StringRes val message: Int) : TodoEditEvent 78 | data object TodoUpdated : TodoEditEvent 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.theme 2 | 3 | import android.os.Build 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.darkColorScheme 7 | import androidx.compose.material3.dynamicDarkColorScheme 8 | import androidx.compose.material3.dynamicLightColorScheme 9 | import androidx.compose.material3.lightColorScheme 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.platform.LocalContext 12 | 13 | private val DarkColorScheme = darkColorScheme( 14 | primary = BrownGrey30, 15 | onPrimary = White, 16 | primaryContainer = Brown30, 17 | onPrimaryContainer = White700, 18 | inversePrimary = Brown40, 19 | secondary = DarkBrown80, 20 | onSecondary = DarkBrown20, 21 | secondaryContainer = DarkBrown30, 22 | onSecondaryContainer = DarkBrown90, 23 | tertiary = Violet80, 24 | onTertiary = Violet20, 25 | tertiaryContainer = Violet30, 26 | onTertiaryContainer = Violet90, 27 | error = Red80, 28 | onError = Red20, 29 | errorContainer = Red30, 30 | onErrorContainer = Red90, 31 | background = DarkGray, 32 | onBackground = White700, 33 | surface = DarkGray200, 34 | onSurface = White700, 35 | inverseSurface = Grey20, 36 | inverseOnSurface = Grey95, 37 | surfaceVariant = DarkGray200, 38 | onSurfaceVariant = White700, 39 | outline = BrownGrey80 40 | ) 41 | 42 | private val LightColorScheme = lightColorScheme( 43 | primary = Brown40, 44 | onPrimary = White, 45 | primaryContainer = Brown90, 46 | onPrimaryContainer = Brown10, 47 | inversePrimary = Brown80, 48 | secondary = DarkBrown40, 49 | onSecondary = White, 50 | secondaryContainer = DarkBrown90, 51 | onSecondaryContainer = DarkBrown10, 52 | tertiary = Violet40, 53 | onTertiary = White, 54 | tertiaryContainer = Violet90, 55 | onTertiaryContainer = Violet10, 56 | error = Red40, 57 | onError = White, 58 | errorContainer = Red90, 59 | onErrorContainer = Red10, 60 | background = Grey99, 61 | onBackground = Grey10, 62 | surface = BrownGrey90, 63 | onSurface = BrownGrey30, 64 | inverseSurface = Grey20, 65 | inverseOnSurface = Grey95, 66 | surfaceVariant = BrownGrey90, 67 | onSurfaceVariant = BrownGrey30, 68 | outline = BrownGrey50 69 | ) 70 | 71 | @Composable 72 | fun TodoTheme( 73 | darkTheme: Boolean = isSystemInDarkTheme(), 74 | dynamicColor: Boolean = true, 75 | content: @Composable () -> Unit 76 | ) { 77 | val colorScheme = when { 78 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 79 | val context = LocalContext.current 80 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 81 | } 82 | 83 | darkTheme -> DarkColorScheme 84 | else -> LightColorScheme 85 | } 86 | 87 | MaterialTheme( 88 | colorScheme = colorScheme, 89 | typography = Typography, 90 | content = content 91 | ) 92 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/local/todo/dao/TodoDao.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.local.todo.dao 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Insert 7 | import androidx.room.OnConflictStrategy 8 | import androidx.room.Query 9 | import androidx.room.Update 10 | import kotlinx.coroutines.flow.Flow 11 | import xyz.teamgravity.todo.core.constant.TodoSort 12 | import xyz.teamgravity.todo.data.local.todo.constant.TodoDatabaseConst.TABLE_TODO 13 | import xyz.teamgravity.todo.data.local.todo.entity.TodoEntity 14 | 15 | @Dao 16 | interface TodoDao { 17 | 18 | /////////////////////////////////////////////////////////////////////////// 19 | // Insert 20 | /////////////////////////////////////////////////////////////////////////// 21 | 22 | @Insert(onConflict = OnConflictStrategy.REPLACE) 23 | suspend fun insertTodo(todo: TodoEntity) 24 | 25 | /////////////////////////////////////////////////////////////////////////// 26 | // Update 27 | /////////////////////////////////////////////////////////////////////////// 28 | 29 | @Update(onConflict = OnConflictStrategy.REPLACE) 30 | suspend fun updateTodo(todo: TodoEntity) 31 | 32 | /////////////////////////////////////////////////////////////////////////// 33 | // Delete 34 | /////////////////////////////////////////////////////////////////////////// 35 | 36 | @Delete 37 | suspend fun deleteTodo(todo: TodoEntity) 38 | 39 | @Query("DELETE FROM $TABLE_TODO WHERE `completed` = 1") 40 | suspend fun deleteAllCompletedTodo() 41 | 42 | @Query("DELETE FROM $TABLE_TODO") 43 | suspend fun deleteAllTodo() 44 | 45 | /////////////////////////////////////////////////////////////////////////// 46 | // Get 47 | /////////////////////////////////////////////////////////////////////////// 48 | 49 | fun getTodos( 50 | query: String, 51 | hideCompleted: Boolean, 52 | sorting: TodoSort 53 | ): PagingSource { 54 | return when (sorting) { 55 | TodoSort.Name -> getTodosSortedByName( 56 | query = query, 57 | hideCompleted = hideCompleted 58 | ) 59 | 60 | TodoSort.Date -> getTodosSortedByDate( 61 | query = query, 62 | hideCompleted = hideCompleted 63 | ) 64 | } 65 | } 66 | 67 | @Query("SELECT * FROM $TABLE_TODO WHERE (`completed` != :hideCompleted OR `completed` = 0) AND `name` LIKE '%' || :query || '%' ORDER BY `important` DESC, `name` ASC") 68 | fun getTodosSortedByName( 69 | query: String, 70 | hideCompleted: Boolean 71 | ): PagingSource 72 | 73 | @Query("SELECT * FROM $TABLE_TODO WHERE (`completed` != :hideCompleted OR `completed` = 0) AND `name` LIKE '%' || :query || '%' ORDER BY `important` DESC, `timestamp` ASC") 74 | fun getTodosSortedByDate( 75 | query: String, 76 | hideCompleted: Boolean 77 | ): PagingSource 78 | 79 | /////////////////////////////////////////////////////////////////////////// 80 | // Misc 81 | /////////////////////////////////////////////////////////////////////////// 82 | 83 | @Query("SELECT EXISTS (SELECT 1 FROM $TABLE_TODO)") 84 | fun isTodoExists(): Flow 85 | 86 | @Query("SELECT EXISTS (SELECT 1 FROM $TABLE_TODO WHERE `completed` = 1)") 87 | fun isCompletedTodoExists(): Flow 88 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/data/repository/TodoRepository.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.data.repository 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import androidx.paging.map 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.map 10 | import kotlinx.coroutines.withContext 11 | import xyz.teamgravity.todo.core.constant.TodoSort 12 | import xyz.teamgravity.todo.data.local.todo.dao.TodoDao 13 | import xyz.teamgravity.todo.data.mapper.toEntity 14 | import xyz.teamgravity.todo.data.mapper.toModel 15 | import xyz.teamgravity.todo.data.model.TodoModel 16 | 17 | class TodoRepository( 18 | private val dao: TodoDao, 19 | private val config: PagingConfig 20 | ) { 21 | 22 | /////////////////////////////////////////////////////////////////////////// 23 | // Insert 24 | /////////////////////////////////////////////////////////////////////////// 25 | 26 | suspend fun insertTodo(todo: TodoModel) { 27 | withContext(Dispatchers.IO) { 28 | dao.insertTodo(todo.toEntity()) 29 | } 30 | } 31 | 32 | /////////////////////////////////////////////////////////////////////////// 33 | // Update 34 | /////////////////////////////////////////////////////////////////////////// 35 | 36 | suspend fun updateTodo(todo: TodoModel) { 37 | withContext(Dispatchers.IO) { 38 | dao.updateTodo(todo.toEntity()) 39 | } 40 | } 41 | 42 | /////////////////////////////////////////////////////////////////////////// 43 | // Delete 44 | /////////////////////////////////////////////////////////////////////////// 45 | 46 | suspend fun deleteTodo(todo: TodoModel) { 47 | withContext(Dispatchers.IO) { 48 | dao.deleteTodo(todo.toEntity()) 49 | } 50 | } 51 | 52 | suspend fun deleteAllCompletedTodo() { 53 | withContext(Dispatchers.IO) { 54 | dao.deleteAllCompletedTodo() 55 | } 56 | } 57 | 58 | suspend fun deleteAllTodo() { 59 | withContext(Dispatchers.IO) { 60 | dao.deleteAllTodo() 61 | } 62 | } 63 | 64 | /////////////////////////////////////////////////////////////////////////// 65 | // Get 66 | /////////////////////////////////////////////////////////////////////////// 67 | 68 | fun getTodos( 69 | query: String, 70 | hideCompleted: Boolean, 71 | sorting: TodoSort 72 | ): Flow> { 73 | return Pager( 74 | config = config, 75 | pagingSourceFactory = { 76 | dao.getTodos( 77 | query = query, 78 | hideCompleted = hideCompleted, 79 | sorting = sorting 80 | ) 81 | } 82 | ).flow.map { entities -> 83 | entities.map { entity -> 84 | entity.toModel() 85 | } 86 | } 87 | } 88 | 89 | /////////////////////////////////////////////////////////////////////////// 90 | // Misc 91 | /////////////////////////////////////////////////////////////////////////// 92 | 93 | fun isTodoExists(): Flow { 94 | return dao.isTodoExists() 95 | } 96 | 97 | fun isCompletedTodoExists(): Flow { 98 | return dao.isCompletedTodoExists() 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/main/res/values-en/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ToDo 3 | 4 | 5 | Stay organized and efficient! 6 | 7 | 8 | Tasks 9 | Task deleted successfully 10 | Undo 11 | Search… 12 | Sort by name 13 | Sort by date 14 | Hide completed 15 | Delete all completed tasks 16 | Delete all tasks 17 | About me 18 | Share 19 | Want to boost your productivity? Try ToDo -> %1$s 20 | Choose 21 | Rate 22 | Source code 23 | Connect us 24 | Change language 25 | Tasks you add appear here 26 | 27 | 28 | New task 29 | Task name 30 | Important task 31 | Name cannot be empty 32 | 33 | 34 | Edit task 35 | Created on: %1$s 36 | 37 | 38 | Confirm deletion 39 | Do you want to delete all completed tasks? 40 | Do you want to delete all tasks? 41 | No 42 | Yes 43 | 44 | 45 | Need help? 46 | Connect us 47 | Let us know if you\'re facing a problem or have suggestions to improve the app. We care about our users! 48 | via Telegram 49 | via Email 50 | Suggestions for ToDo 51 | 52 | 53 | Important task 54 | Add task 55 | Go back 56 | Save task 57 | More actions 58 | App icon 59 | Company logo 60 | Sort tasks 61 | Tasks are sorted by %1$s 62 | Search tasks 63 | Hide completed tasks 64 | User support 65 | Connect us via Telegram 66 | Connect us via Email 67 | 68 | 69 | Add ToDo 70 | Quickly add ToDo by pressing this action 71 | Quick add is currently disabled 72 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ToDo 3 | Raheem 4 | 5 | 6 | Stay organized and efficient! 7 | 8 | 9 | Tasks 10 | Task deleted successfully 11 | Undo 12 | Search… 13 | Sort by name 14 | Sort by date 15 | Hide completed 16 | Delete all completed tasks 17 | Delete all tasks 18 | About me 19 | Share 20 | Want to boost your productivity? Try ToDo -> %1$s 21 | Choose 22 | Rate 23 | Source code 24 | Connect us 25 | Change language 26 | Tasks you add appear here 27 | 28 | 29 | New task 30 | Task name 31 | Important task 32 | Name cannot be empty 33 | 34 | 35 | Edit task 36 | Created on: %1$s 37 | 38 | 39 | Confirm deletion 40 | Do you want to delete all completed tasks? 41 | Do you want to delete all tasks? 42 | No 43 | Yes 44 | 45 | 46 | Need help? 47 | Connect us 48 | Let us know if you\'re facing a problem or have suggestions to improve the app. We care about our users! 49 | via Telegram 50 | via Email 51 | Suggestions for ToDo 52 | 53 | 54 | Important task 55 | Add task 56 | Go back 57 | Save task 58 | More actions 59 | App icon 60 | Company logo 61 | Sort tasks 62 | Tasks are sorted by %1$s 63 | Search tasks 64 | Hide completed tasks 65 | User support 66 | Connect us via Telegram 67 | Connect us via Email 68 | 69 | 70 | Add ToDo 71 | Quickly add ToDo by pressing this action 72 | Quick add is currently disabled 73 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Задания 3 | 4 | 5 | Будьте организованы и эффективны! 6 | 7 | 8 | Задачи 9 | Задача успешно удалена 10 | Отменить 11 | Поиск… 12 | Сортировать по имени 13 | Сортировать по дате 14 | Скрыть выполненные 15 | Удалить все выполненные задачи 16 | Удалить все задачи 17 | Обо мне 18 | Поделиться 19 | Хотите повысить свою продуктивность? Попробуйте ToDo -> %1$s 20 | Выберите 21 | Оценить 22 | Исходный код 23 | Связаться с нами 24 | Изменить язык 25 | Задачи, которые вы добавите, появятся здесь 26 | 27 | 28 | Новая задача 29 | Название задачи 30 | Важная задача 31 | Название не может быть пустым 32 | 33 | 34 | Редактировать задачу 35 | Создано: %1$s 36 | 37 | 38 | Подтвердить удаление 39 | Хотите ли вы удалить все выполненные задачи? 40 | Хотите ли вы удалить все задачи? 41 | Нет 42 | Да 43 | 44 | 45 | Нужна помощь? 46 | Связаться с нами 47 | Сообщите нам, если у вас возникла проблема или есть предложения по улучшению приложения. Мы заботимся о наших пользователях! 48 | через Телеграм 49 | через Эл. почту 50 | Предложения для ToDo 51 | 52 | 53 | Важная задача 54 | Добавить задачу 55 | Назад 56 | Сохранить задачу 57 | Дополнительные действия 58 | Иконка приложения 59 | Логотип компании 60 | Сортировать задачи 61 | Задачи отсортированы по %1$s 62 | Поиск задач 63 | Скрыть выполненные задачи 64 | Поддержка пользователей 65 | Связаться через Telegram 66 | Связаться через Email 67 | 68 | 69 | Добавить задачу 70 | Быстро добавить задачу, нажав на действие 71 | Быстрое добавление сейчас недоступно 72 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/card/CardTodo.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.card 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.size 10 | import androidx.compose.foundation.layout.width 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.rounded.Delete 13 | import androidx.compose.material3.Card 14 | import androidx.compose.material3.Checkbox 15 | import androidx.compose.material3.MaterialTheme 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.graphics.Color 21 | import androidx.compose.ui.res.painterResource 22 | import androidx.compose.ui.res.stringResource 23 | import androidx.compose.ui.text.style.TextDecoration 24 | import androidx.compose.ui.text.style.TextOverflow 25 | import androidx.compose.ui.unit.dp 26 | import xyz.teamgravity.coresdkcompose.swipe.SwipeToDelete 27 | import xyz.teamgravity.todo.R 28 | import xyz.teamgravity.todo.data.model.TodoModel 29 | import xyz.teamgravity.todo.presentation.theme.Black 30 | import xyz.teamgravity.todo.presentation.theme.White 31 | 32 | @Composable 33 | fun CardTodo( 34 | todo: TodoModel, 35 | onClick: (todo: TodoModel) -> Unit, 36 | onCheckedChange: (todo: TodoModel, checked: Boolean) -> Unit, 37 | onDismiss: (todo: TodoModel) -> Unit 38 | ) { 39 | SwipeToDelete( 40 | model = todo, 41 | onDelete = onDismiss, 42 | duration = 700, 43 | deleteIcon = Icons.Rounded.Delete, 44 | enableDeleteFromStartToEnd = true, 45 | enableDeleteFromEndToStart = true, 46 | positionalThreshold = 0.35F, 47 | inactiveBackgroundColor = Color.LightGray, 48 | activeBackgroundColor = MaterialTheme.colorScheme.error, 49 | inactiveIconTint = Black, 50 | activeIconTint = White 51 | ) { 52 | Card( 53 | onClick = { 54 | onClick(todo) 55 | }, 56 | modifier = Modifier 57 | .fillMaxWidth() 58 | .padding(horizontal = 5.dp, vertical = 2.dp) 59 | ) { 60 | Row( 61 | verticalAlignment = Alignment.CenterVertically, 62 | modifier = Modifier 63 | .fillMaxSize() 64 | .padding(2.dp) 65 | ) { 66 | Checkbox( 67 | checked = todo.completed, 68 | onCheckedChange = { checked -> 69 | onCheckedChange(todo, checked) 70 | } 71 | ) 72 | Text( 73 | text = todo.name, 74 | maxLines = 1, 75 | overflow = TextOverflow.Ellipsis, 76 | style = MaterialTheme.typography.bodyMedium, 77 | textDecoration = if (todo.completed) TextDecoration.LineThrough else TextDecoration.None, 78 | modifier = Modifier.weight(1F) 79 | ) 80 | if (todo.important) { 81 | Spacer( 82 | modifier = Modifier.width(10.dp) 83 | ) 84 | Image( 85 | painter = painterResource(id = R.drawable.ic_warn), 86 | contentDescription = stringResource(id = R.string.cd_task_important), 87 | modifier = Modifier.size(24.dp) 88 | ) 89 | } 90 | Spacer( 91 | modifier = Modifier.width(8.dp) 92 | ) 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/res/values-uz/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vazifalar 4 | 5 | 6 | Tartibli va samarali bo\'ling! 7 | 8 | 9 | Vazifalar 10 | Vazifa muvaffaqiyatli o\'chirildi 11 | Qaytarish 12 | Qidirish… 13 | Nomdan saralash 14 | Sanadan saralash 15 | Bajarilganlarni yashirish 16 | Bajarilganlarni o\'chirish 17 | Hammasini o\'chirish 18 | Dastur haqida 19 | Ulashish 20 | Samaradorligingizni oshirishni xohlaysizmi? Unda ToDo dan foydalanib ko\'ring -> %1$s 21 | Tanlang 22 | Baholash 23 | Dastur kodi 24 | Yordam 25 | Tilni o\'zgartirish 26 | Siz qo\'shgan vazifalar bu yerda ko\'rinadi 27 | 28 | 29 | Yangi vazifa 30 | Vazifa nomi 31 | Muhim vazifa 32 | Nomni to\'ldirish majburiy 33 | 34 | 35 | Vazifani tahrirlash 36 | Yaratilgan: %1$s 37 | 38 | 39 | O\'chirishni tasdiqlang 40 | Hamma bajarilgan vazifalarni o\'chirishga rozimisiz? 41 | Hamma vazifalarni o\'chirishga rozimisiz? 42 | Yo\'q 43 | Ha 44 | 45 | 46 | Yordam kerakmi? 47 | Biz bilan bog\'laning 48 | Taklifingizni yoki qanday muammoga duch kelayotganingizni bizga xabar bering. Biz foydalanuvchilarimiz haqida qayg\'uramiz! 49 | Telegram orqali 50 | Email orqali 51 | ToDo uchun takliflar 52 | 53 | 54 | Muhim vazifa 55 | Vazifa qo\'shish 56 | Orqaga qaytish 57 | Vazifani saqlash 58 | Ko\'proq amallar 59 | Dastur belgisi 60 | Kompaniya emblemasi 61 | Vazifalarni saralash 62 | Vazifalar %1$s orqali saralangan 63 | Vazifalarni qidirish 64 | Bajarilganlarni yashirish 65 | Foydalanuvchilarga yordam berish 66 | Telegram orqali bog\'lanish 67 | Email orqali bog\'lanish 68 | 69 | 70 | Vazifa qo\'shish 71 | Tugmani bosish orqali darhol vazifa qo\'shing 72 | Tez qo‘shish hozirda o‘chirib qo‘yilgan 73 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/component/topbar/TopBarMoreMenu.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.component.topbar 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.rounded.Done 5 | import androidx.compose.material.icons.rounded.MoreVert 6 | import androidx.compose.material3.DropdownMenu 7 | import androidx.compose.runtime.Composable 8 | import xyz.teamgravity.coresdkandroid.android.BuildUtil 9 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 10 | import xyz.teamgravity.coresdkcompose.image.IconPlain 11 | import xyz.teamgravity.coresdkcompose.menu.GDropdownMenuItem 12 | import xyz.teamgravity.todo.R 13 | 14 | @Composable 15 | fun TopBarMoreMenu( 16 | expanded: Boolean, 17 | onExpand: () -> Unit, 18 | onDismiss: () -> Unit, 19 | hideCompleted: Boolean, 20 | onHideCompletedChange: () -> Unit, 21 | deleteCompletedEnabled: Boolean, 22 | onDeleteCompletedClick: () -> Unit, 23 | deleteAllEnabled: Boolean, 24 | onDeleteAllClick: () -> Unit, 25 | onLanguageClick: () -> Unit, 26 | onSupportClick: () -> Unit, 27 | onShareClick: () -> Unit, 28 | onRateClick: () -> Unit, 29 | onSourceCodeClick: () -> Unit, 30 | onAboutClick: () -> Unit 31 | ) { 32 | IconButtonPlain( 33 | onClick = onExpand, 34 | icon = Icons.Rounded.MoreVert, 35 | contentDescription = R.string.cd_more_vertical 36 | ) 37 | DropdownMenu( 38 | expanded = expanded, 39 | onDismissRequest = onDismiss 40 | ) { 41 | GDropdownMenuItem( 42 | onDismiss = onDismiss, 43 | onClick = onHideCompletedChange, 44 | icon = R.drawable.ic_hide, 45 | label = R.string.hide_completed, 46 | trailingIcon = { 47 | if (hideCompleted) { 48 | IconPlain( 49 | icon = Icons.Rounded.Done, 50 | contentDescription = R.string.cd_hide_completed 51 | ) 52 | } 53 | } 54 | ) 55 | GDropdownMenuItem( 56 | onDismiss = onDismiss, 57 | onClick = onDeleteCompletedClick, 58 | icon = R.drawable.ic_delete, 59 | label = R.string.delete_all_completed, 60 | enabled = deleteCompletedEnabled 61 | ) 62 | GDropdownMenuItem( 63 | onDismiss = onDismiss, 64 | onClick = onDeleteAllClick, 65 | icon = R.drawable.ic_delete, 66 | label = R.string.delete_all_tasks, 67 | enabled = deleteAllEnabled 68 | ) 69 | if (BuildUtil.atLeast33()) { 70 | GDropdownMenuItem( 71 | onDismiss = onDismiss, 72 | onClick = onLanguageClick, 73 | icon = R.drawable.ic_language, 74 | label = R.string.change_language 75 | ) 76 | } 77 | GDropdownMenuItem( 78 | onDismiss = onDismiss, 79 | onClick = onSupportClick, 80 | icon = R.drawable.ic_customer_service, 81 | label = R.string.support 82 | ) 83 | GDropdownMenuItem( 84 | onDismiss = onDismiss, 85 | onClick = onShareClick, 86 | icon = R.drawable.ic_share, 87 | label = R.string.share 88 | ) 89 | GDropdownMenuItem( 90 | onDismiss = onDismiss, 91 | onClick = onRateClick, 92 | icon = R.drawable.ic_star, 93 | label = R.string.rate 94 | ) 95 | GDropdownMenuItem( 96 | onDismiss = onDismiss, 97 | onClick = onSourceCodeClick, 98 | icon = R.drawable.ic_github, 99 | label = R.string.source_code 100 | ) 101 | GDropdownMenuItem( 102 | onDismiss = onDismiss, 103 | onClick = onAboutClick, 104 | icon = R.drawable.ic_info, 105 | label = R.string.about_me 106 | ) 107 | } 108 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/injection/provide/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.injection.provide 2 | 3 | import android.app.Application 4 | import androidx.paging.PagingConfig 5 | import androidx.room.Room 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.SupervisorJob 13 | import timber.log.Timber 14 | import xyz.teamgravity.coresdkandroid.crypto.CryptoManager 15 | import xyz.teamgravity.coresdkandroid.preferences.Preferences 16 | import xyz.teamgravity.coresdkandroid.review.ReviewManager 17 | import xyz.teamgravity.coresdkandroid.update.UpdateManager 18 | import xyz.teamgravity.todo.core.constant.PagingConst 19 | import xyz.teamgravity.todo.data.local.preferences.AppPreferences 20 | import xyz.teamgravity.todo.data.local.todo.callback.TodoCallback 21 | import xyz.teamgravity.todo.data.local.todo.constant.TodoDatabaseConst 22 | import xyz.teamgravity.todo.data.local.todo.dao.TodoDao 23 | import xyz.teamgravity.todo.data.local.todo.database.TodoDatabase 24 | import xyz.teamgravity.todo.data.repository.TodoRepository 25 | import xyz.teamgravity.todo.injection.name.ApplicationScope 26 | import javax.inject.Provider 27 | import javax.inject.Singleton 28 | 29 | @Module 30 | @InstallIn(SingletonComponent::class) 31 | object ApplicationModule { 32 | 33 | @Provides 34 | @Singleton 35 | fun provideTodoDatabase( 36 | application: Application, 37 | todoCallback: TodoCallback 38 | ): TodoDatabase = Room.databaseBuilder( 39 | context = application, 40 | klass = TodoDatabase::class.java, 41 | name = TodoDatabaseConst.NAME 42 | ).addMigrations() 43 | .addCallback(todoCallback) 44 | .build() 45 | 46 | @Provides 47 | @Singleton 48 | @ApplicationScope 49 | fun provideApplicationScope(): CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) 50 | 51 | @Provides 52 | @Singleton 53 | fun provideTodoCallback( 54 | application: Application, 55 | todoDatabase: Provider, 56 | @ApplicationScope applicationScope: CoroutineScope 57 | ): TodoCallback = TodoCallback( 58 | application = application, 59 | db = todoDatabase, 60 | scope = applicationScope 61 | ) 62 | 63 | @Provides 64 | @Singleton 65 | fun provideTodoDao(todoDatabase: TodoDatabase): TodoDao = todoDatabase.todoDao() 66 | 67 | @Provides 68 | @Singleton 69 | fun providePagingConfig(): PagingConfig = PagingConfig( 70 | pageSize = PagingConst.PAGE_SIZE, 71 | prefetchDistance = PagingConst.PREFETCH_DISTANCE, 72 | maxSize = PagingConst.MAX_SIZE, 73 | enablePlaceholders = PagingConst.ENABLE_PLACEHOLDERS 74 | ) 75 | 76 | @Provides 77 | @Singleton 78 | fun provideTodoRepository( 79 | todoDao: TodoDao, 80 | pagingConfig: PagingConfig 81 | ): TodoRepository = TodoRepository( 82 | dao = todoDao, 83 | config = pagingConfig 84 | ) 85 | 86 | @Provides 87 | @Singleton 88 | fun provideTimberDebugTree(): Timber.DebugTree = Timber.DebugTree() 89 | 90 | @Provides 91 | @Singleton 92 | fun provideUpdateManager(application: Application): UpdateManager = UpdateManager(application) 93 | 94 | @Provides 95 | @Singleton 96 | fun provideCryptoManager(): CryptoManager = CryptoManager() 97 | 98 | @Provides 99 | @Singleton 100 | fun providePreferences( 101 | cryptoManager: CryptoManager, 102 | application: Application 103 | ): Preferences = Preferences( 104 | crypto = cryptoManager, 105 | context = application 106 | ) 107 | 108 | @Provides 109 | @Singleton 110 | fun provideReviewManager( 111 | preferences: Preferences, 112 | application: Application 113 | ): ReviewManager = ReviewManager( 114 | preferences = preferences, 115 | context = application 116 | ) 117 | 118 | @Provides 119 | @Singleton 120 | fun provideAppPreferences(preferences: Preferences): AppPreferences = AppPreferences(preferences) 121 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/todo/add/TodoAddScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.todo.add 2 | 3 | import androidx.activity.compose.LocalOnBackPressedDispatcherOwner 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.WindowInsets 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.safeDrawing 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 11 | import androidx.compose.material.icons.rounded.Done 12 | import androidx.compose.material3.Scaffold 13 | import androidx.compose.material3.Snackbar 14 | import androidx.compose.material3.SnackbarHost 15 | import androidx.compose.material3.SnackbarHostState 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.platform.LocalContext 20 | import androidx.hilt.navigation.compose.hiltViewModel 21 | import com.ramcosta.composedestinations.annotation.Destination 22 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator 23 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 24 | import xyz.teamgravity.coresdkcompose.observe.ObserveEvent 25 | import xyz.teamgravity.coresdkcompose.text.TextPlain 26 | import xyz.teamgravity.todo.R 27 | import xyz.teamgravity.todo.presentation.component.button.TodoFloatingActionButton 28 | import xyz.teamgravity.todo.presentation.component.misc.TodoConfigure 29 | import xyz.teamgravity.todo.presentation.component.topbar.TopBar 30 | import xyz.teamgravity.todo.presentation.navigation.MainNavGraph 31 | 32 | @Destination 33 | @Composable 34 | fun TodoAddScreen( 35 | snackbar: SnackbarHostState = remember { SnackbarHostState() }, 36 | navigator: DestinationsNavigator, 37 | viewmodel: TodoAddViewModel = hiltViewModel() 38 | ) { 39 | val context = LocalContext.current 40 | val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher 41 | 42 | ObserveEvent( 43 | flow = viewmodel.event, 44 | onEvent = { event -> 45 | when (event) { 46 | is TodoAddViewModel.TodoAddEvent.InvalidInput -> { 47 | snackbar.showSnackbar(context.getString(event.message)) 48 | } 49 | 50 | TodoAddViewModel.TodoAddEvent.TodoAdded -> { 51 | navigator.popBackStack() 52 | } 53 | } 54 | } 55 | ) 56 | 57 | Scaffold( 58 | topBar = { 59 | TopBar( 60 | title = { 61 | TextPlain( 62 | id = R.string.new_task 63 | ) 64 | }, 65 | navigationIcon = { 66 | IconButtonPlain( 67 | onClick = { 68 | dispatcher?.onBackPressed() ?: navigator.navigateUp() 69 | }, 70 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 71 | contentDescription = R.string.cd_back_button 72 | ) 73 | } 74 | ) 75 | }, 76 | floatingActionButton = { 77 | TodoFloatingActionButton( 78 | onClick = viewmodel::onSaveTodo, 79 | icon = Icons.Rounded.Done, 80 | contentDescription = R.string.cd_done_button 81 | ) 82 | }, 83 | snackbarHost = { 84 | SnackbarHost( 85 | hostState = snackbar 86 | ) { data -> 87 | Snackbar( 88 | snackbarData = data 89 | ) 90 | } 91 | }, 92 | contentWindowInsets = WindowInsets.safeDrawing 93 | ) { padding -> 94 | Box( 95 | modifier = Modifier 96 | .fillMaxSize() 97 | .padding(padding), 98 | ) { 99 | TodoConfigure( 100 | name = viewmodel.name, 101 | onNameChange = viewmodel::onNameChange, 102 | important = viewmodel.important, 103 | onImportantChange = viewmodel::onImportantChange 104 | ) 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | compose = "2025.07.00" 4 | compose-activity = "1.10.1" 5 | compose-lifecycle = "2.9.2" 6 | compose-viewmodel = "2.9.2" 7 | compose-hilt = "1.2.0" 8 | compose-paging = "3.3.6" 9 | core = "1.16.0" 10 | splash = "1.0.1" 11 | shortcut = "1.1.0" 12 | collections = "0.4.0" 13 | firebase = "33.16.0" 14 | hilt = "2.57" 15 | coroutines = "1.10.2" 16 | room = "2.7.2" 17 | destinations = "2.2.0" 18 | timber = "5.0.1" 19 | gravity-core = "1.0.33" 20 | gravity-core-compose = "1.0.28" 21 | 22 | android = "8.12.0" 23 | kotlin = "2.2.0" 24 | ksp = "2.2.0-2.0.2" 25 | gms = "4.4.3" 26 | crashlytics = "3.0.5" 27 | 28 | sdk-min = "26" 29 | sdk-compile = "36" 30 | sdk-target = "36" 31 | 32 | [libraries] 33 | 34 | compose = { group = "androidx.compose", name = "compose-bom", version.ref = "compose" } 35 | compose-ui = { group = "androidx.compose.ui", name = "ui" } 36 | compose-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } 37 | compose-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } 38 | compose-material3 = { group = "androidx.compose.material3", name = "material3" } 39 | compose-icons = { group = "androidx.compose.material", name = "material-icons-extended" } 40 | compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" } 41 | compose-lifecycle = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "compose-lifecycle" } 42 | compose-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "compose-viewmodel" } 43 | compose-hilt = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "compose-hilt" } 44 | compose-paging = { group = "androidx.paging", name = "paging-compose", version.ref = "compose-paging" } 45 | core = { group = "androidx.core", name = "core-ktx", version.ref = "core" } 46 | splash = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash" } 47 | shortcut = { group = "androidx.core", name = "core-google-shortcuts", version.ref = "shortcut" } 48 | collections = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collections" } 49 | firebase = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase" } 50 | firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } 51 | firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } 52 | firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" } 53 | hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } 54 | hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } 55 | coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } 56 | coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } 57 | room = { group = "androidx.room", name = "room-runtime", version.ref = "room" } 58 | room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } 59 | room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" } 60 | room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } 61 | destinations = { group = "io.github.raamcosta.compose-destinations", name = "core", version.ref = "destinations" } 62 | destinations-compiler = { group = "io.github.raamcosta.compose-destinations", name = "ksp", version.ref = "destinations" } 63 | timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } 64 | gravity-core = { group = "com.github.raheemadamboev", name = "core-sdk-android", version.ref = "gravity-core" } 65 | gravity-core-compose = { group = "com.github.raheemadamboev", name = "core-sdk-compose", version.ref = "gravity-core-compose" } 66 | 67 | [plugins] 68 | 69 | android = { id = "com.android.application", version.ref = "android" } 70 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 71 | compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 72 | parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } 73 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 74 | gms = { id = "com.google.gms.google-services", version.ref = "gms" } 75 | crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" } 76 | hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } 77 | room = { id = "androidx.room", version.ref = "room" } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | 3 | plugins { 4 | alias(libs.plugins.android) 5 | alias(libs.plugins.kotlin) 6 | alias(libs.plugins.compose) 7 | alias(libs.plugins.parcelize) 8 | alias(libs.plugins.ksp) 9 | alias(libs.plugins.gms) 10 | alias(libs.plugins.crashlytics) 11 | alias(libs.plugins.hilt) 12 | alias(libs.plugins.room) 13 | } 14 | 15 | android { 16 | namespace = "xyz.teamgravity.todo" 17 | compileSdk = libs.versions.sdk.compile.get().toInt() 18 | 19 | defaultConfig { 20 | applicationId = "xyz.teamgravity.todo" 21 | minSdk = libs.versions.sdk.min.get().toInt() 22 | targetSdk = libs.versions.sdk.target.get().toInt() 23 | versionCode = 21 24 | versionName = "1.2.10" 25 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 26 | 27 | vectorDrawables { 28 | useSupportLibrary = true 29 | } 30 | 31 | ndk { 32 | debugSymbolLevel = "FULL" 33 | abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86_64", "x86") 34 | } 35 | 36 | bundle { 37 | language { 38 | enableSplit = false 39 | } 40 | } 41 | 42 | androidResources { 43 | generateLocaleConfig = true 44 | localeFilters.clear() 45 | localeFilters += setOf("en", "ru", "uz") 46 | } 47 | } 48 | 49 | buildTypes { 50 | release { 51 | isMinifyEnabled = true 52 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 53 | } 54 | 55 | debug { 56 | applicationIdSuffix = ".debug" 57 | } 58 | } 59 | 60 | compileOptions { 61 | sourceCompatibility = JavaVersion.VERSION_17 62 | targetCompatibility = JavaVersion.VERSION_17 63 | } 64 | 65 | kotlin { 66 | target { 67 | compilerOptions { 68 | jvmTarget.set(JvmTarget.JVM_17) 69 | freeCompilerArgs.addAll( 70 | "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", 71 | "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" 72 | ) 73 | } 74 | } 75 | } 76 | 77 | buildFeatures { 78 | compose = true 79 | buildConfig = true 80 | } 81 | 82 | packaging { 83 | resources { 84 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 85 | } 86 | } 87 | 88 | room { 89 | schemaDirectory("$projectDir/schemas") 90 | } 91 | } 92 | 93 | dependencies { 94 | 95 | // compose 96 | implementation(platform(libs.compose)) 97 | implementation(libs.compose.ui) 98 | implementation(libs.compose.graphics) 99 | implementation(libs.compose.preview) 100 | implementation(libs.compose.material3) 101 | implementation(libs.compose.icons) 102 | 103 | // compose activity 104 | implementation(libs.compose.activity) 105 | 106 | // compose lifecycle 107 | implementation(libs.compose.lifecycle) 108 | 109 | // compose viewmodel 110 | implementation(libs.compose.viewmodel) 111 | 112 | // compose hilt 113 | implementation(libs.compose.hilt) 114 | 115 | // compose paging 116 | implementation(libs.compose.paging) 117 | 118 | // core 119 | implementation(libs.core) 120 | 121 | // splash 122 | implementation(libs.splash) 123 | 124 | // shortcut 125 | implementation(libs.shortcut) 126 | 127 | // collections 128 | implementation(libs.collections) 129 | 130 | // firebase 131 | implementation(platform(libs.firebase)) 132 | implementation(libs.firebase.analytics) 133 | implementation(libs.firebase.crashlytics) 134 | implementation(libs.firebase.messaging) 135 | 136 | // hilt 137 | implementation(libs.hilt) 138 | ksp(libs.hilt.compiler) 139 | 140 | // coroutines 141 | implementation(libs.coroutines) 142 | implementation(libs.coroutines.android) 143 | 144 | // room 145 | implementation(libs.room) 146 | implementation(libs.room.ktx) 147 | implementation(libs.room.paging) 148 | ksp(libs.room.compiler) 149 | 150 | // destinations 151 | implementation(libs.destinations) 152 | ksp(libs.destinations.compiler) 153 | 154 | // timber 155 | implementation(libs.timber) 156 | 157 | // gravity 158 | implementation(libs.gravity.core) 159 | implementation(libs.gravity.core.compose) 160 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 70 | -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/about/AboutLandscapeScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.about 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.Spacer 8 | import androidx.compose.foundation.layout.WindowInsets 9 | import androidx.compose.foundation.layout.fillMaxHeight 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.height 12 | import androidx.compose.foundation.layout.padding 13 | import androidx.compose.foundation.layout.safeDrawing 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.foundation.layout.width 16 | import androidx.compose.material.icons.Icons 17 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.material3.Scaffold 20 | import androidx.compose.material3.Text 21 | import androidx.compose.runtime.Composable 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.layout.ContentScale 25 | import androidx.compose.ui.res.painterResource 26 | import androidx.compose.ui.res.stringResource 27 | import androidx.compose.ui.text.font.FontWeight 28 | import androidx.compose.ui.unit.dp 29 | import androidx.compose.ui.unit.sp 30 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 31 | import xyz.teamgravity.coresdkcompose.text.TextPlain 32 | import xyz.teamgravity.todo.BuildConfig 33 | import xyz.teamgravity.todo.R 34 | import xyz.teamgravity.todo.presentation.component.topbar.TopBar 35 | 36 | @Composable 37 | fun AboutLandscapeScreen( 38 | onBackButtonClick: () -> Unit 39 | ) { 40 | Scaffold( 41 | topBar = { 42 | TopBar( 43 | title = { 44 | TextPlain( 45 | id = R.string.app_name 46 | ) 47 | }, 48 | navigationIcon = { 49 | IconButtonPlain( 50 | onClick = onBackButtonClick, 51 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 52 | contentDescription = R.string.cd_back_button 53 | ) 54 | } 55 | ) 56 | }, 57 | contentWindowInsets = WindowInsets.safeDrawing 58 | ) { padding -> 59 | Row( 60 | horizontalArrangement = Arrangement.SpaceEvenly, 61 | verticalAlignment = Alignment.CenterVertically, 62 | modifier = Modifier 63 | .fillMaxSize() 64 | .padding(padding) 65 | ) { 66 | Image( 67 | painter = painterResource(id = R.drawable.icon), 68 | contentDescription = stringResource(id = R.string.cd_app_icon), 69 | contentScale = ContentScale.Crop, 70 | modifier = Modifier.size(180.dp) 71 | ) 72 | Column( 73 | horizontalAlignment = Alignment.CenterHorizontally, 74 | verticalArrangement = Arrangement.Center, 75 | modifier = Modifier.fillMaxHeight() 76 | ) { 77 | Text( 78 | text = stringResource(id = R.string.app_name), 79 | style = MaterialTheme.typography.displayMedium, 80 | fontWeight = FontWeight.Bold 81 | ) 82 | Text( 83 | text = BuildConfig.VERSION_NAME, 84 | style = MaterialTheme.typography.bodyMedium 85 | ) 86 | Spacer( 87 | modifier = Modifier.height(16.dp), 88 | ) 89 | Column( 90 | horizontalAlignment = Alignment.CenterHorizontally, 91 | verticalArrangement = Arrangement.spacedBy(5.dp), 92 | modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp) 93 | ) { 94 | Image( 95 | painter = painterResource(id = R.drawable.gravity), 96 | contentDescription = stringResource(id = R.string.cd_company_logo), 97 | modifier = Modifier 98 | .width(100.dp) 99 | .height(20.dp) 100 | ) 101 | Text( 102 | text = stringResource(id = R.string.raheem), 103 | fontSize = 12.sp 104 | ) 105 | } 106 | } 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/about/AboutPortraitScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.about 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.WindowInsets 8 | import androidx.compose.foundation.layout.fillMaxSize 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.safeDrawing 13 | import androidx.compose.foundation.layout.size 14 | import androidx.compose.foundation.layout.width 15 | import androidx.compose.material.icons.Icons 16 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.material3.Scaffold 19 | import androidx.compose.material3.Text 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.layout.ContentScale 24 | import androidx.compose.ui.res.painterResource 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.text.font.FontWeight 27 | import androidx.compose.ui.text.style.TextAlign 28 | import androidx.compose.ui.unit.dp 29 | import androidx.compose.ui.unit.sp 30 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 31 | import xyz.teamgravity.coresdkcompose.text.TextPlain 32 | import xyz.teamgravity.todo.BuildConfig 33 | import xyz.teamgravity.todo.R 34 | import xyz.teamgravity.todo.presentation.component.topbar.TopBar 35 | 36 | @Composable 37 | fun AboutPortraitScreen( 38 | onBackButtonClick: () -> Unit 39 | ) { 40 | Scaffold( 41 | topBar = { 42 | TopBar( 43 | title = { 44 | TextPlain( 45 | id = R.string.app_name 46 | ) 47 | }, 48 | navigationIcon = { 49 | IconButtonPlain( 50 | onClick = onBackButtonClick, 51 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 52 | contentDescription = R.string.cd_back_button 53 | ) 54 | } 55 | ) 56 | }, 57 | contentWindowInsets = WindowInsets.safeDrawing 58 | ) { padding -> 59 | Column( 60 | horizontalAlignment = Alignment.CenterHorizontally, 61 | modifier = Modifier 62 | .fillMaxSize() 63 | .padding(padding) 64 | .padding(horizontal = 16.dp) 65 | ) { 66 | Spacer( 67 | modifier = Modifier.weight(1F) 68 | ) 69 | Image( 70 | painter = painterResource(id = R.drawable.icon), 71 | contentDescription = stringResource(id = R.string.cd_app_icon), 72 | contentScale = ContentScale.Crop, 73 | modifier = Modifier.size(180.dp) 74 | ) 75 | Spacer( 76 | modifier = Modifier.height(20.dp) 77 | ) 78 | Text( 79 | text = stringResource(id = R.string.app_name), 80 | style = MaterialTheme.typography.displayMedium, 81 | fontWeight = FontWeight.Bold, 82 | textAlign = TextAlign.Center, 83 | modifier = Modifier.fillMaxWidth() 84 | ) 85 | Spacer( 86 | modifier = Modifier.height(10.dp) 87 | ) 88 | Text( 89 | text = BuildConfig.VERSION_NAME, 90 | style = MaterialTheme.typography.bodyMedium, 91 | textAlign = TextAlign.Center, 92 | modifier = Modifier.fillMaxWidth() 93 | ) 94 | Spacer( 95 | modifier = Modifier.weight(1F) 96 | ) 97 | Column( 98 | horizontalAlignment = Alignment.CenterHorizontally, 99 | verticalArrangement = Arrangement.spacedBy(5.dp), 100 | modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp) 101 | ) { 102 | Image( 103 | painter = painterResource(id = R.drawable.gravity), 104 | contentDescription = stringResource(id = R.string.cd_company_logo), 105 | modifier = Modifier 106 | .width(100.dp) 107 | .height(20.dp) 108 | ) 109 | Text( 110 | text = stringResource(id = R.string.raheem), 111 | fontSize = 12.sp 112 | ) 113 | } 114 | Spacer( 115 | modifier = Modifier.height(16.dp) 116 | ) 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/todo/edit/TodoEditScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.todo.edit 2 | 3 | import androidx.activity.compose.LocalOnBackPressedDispatcherOwner 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.WindowInsets 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.height 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.safeDrawing 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 13 | import androidx.compose.material.icons.rounded.Done 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.material3.Scaffold 16 | import androidx.compose.material3.Snackbar 17 | import androidx.compose.material3.SnackbarHost 18 | import androidx.compose.material3.SnackbarHostState 19 | import androidx.compose.material3.Text 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.remember 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.platform.LocalContext 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.unit.dp 26 | import androidx.hilt.navigation.compose.hiltViewModel 27 | import com.ramcosta.composedestinations.annotation.Destination 28 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator 29 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 30 | import xyz.teamgravity.coresdkcompose.observe.ObserveEvent 31 | import xyz.teamgravity.coresdkcompose.text.TextPlain 32 | import xyz.teamgravity.todo.R 33 | import xyz.teamgravity.todo.data.model.TodoModel 34 | import xyz.teamgravity.todo.presentation.component.button.TodoFloatingActionButton 35 | import xyz.teamgravity.todo.presentation.component.misc.TodoConfigure 36 | import xyz.teamgravity.todo.presentation.component.topbar.TopBar 37 | import xyz.teamgravity.todo.presentation.navigation.MainNavGraph 38 | 39 | @Destination(navArgs = TodoEditScreenArgs::class) 40 | @Composable 41 | fun TodoEditScreen( 42 | snackbar: SnackbarHostState = remember { SnackbarHostState() }, 43 | navigator: DestinationsNavigator, 44 | viewmodel: TodoEditViewModel = hiltViewModel() 45 | ) { 46 | val context = LocalContext.current 47 | val dispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher 48 | 49 | ObserveEvent( 50 | flow = viewmodel.event, 51 | onEvent = { event -> 52 | when (event) { 53 | is TodoEditViewModel.TodoEditEvent.InvalidInput -> { 54 | snackbar.showSnackbar(context.getString(event.message)) 55 | } 56 | 57 | TodoEditViewModel.TodoEditEvent.TodoUpdated -> { 58 | navigator.popBackStack() 59 | } 60 | } 61 | } 62 | ) 63 | 64 | Scaffold( 65 | topBar = { 66 | TopBar( 67 | title = { 68 | TextPlain( 69 | id = R.string.edit_task 70 | ) 71 | }, 72 | navigationIcon = { 73 | IconButtonPlain( 74 | onClick = { 75 | dispatcher?.onBackPressed() ?: navigator.navigateUp() 76 | }, 77 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 78 | contentDescription = R.string.cd_back_button 79 | ) 80 | } 81 | ) 82 | }, 83 | floatingActionButton = { 84 | TodoFloatingActionButton( 85 | onClick = viewmodel::onUpdateTodo, 86 | icon = Icons.Rounded.Done, 87 | contentDescription = R.string.cd_done_button 88 | ) 89 | }, 90 | snackbarHost = { 91 | SnackbarHost( 92 | hostState = snackbar 93 | ) { data -> 94 | Snackbar( 95 | snackbarData = data 96 | ) 97 | } 98 | }, 99 | contentWindowInsets = WindowInsets.safeDrawing 100 | ) { padding -> 101 | Column( 102 | modifier = Modifier 103 | .fillMaxSize() 104 | .padding(padding) 105 | ) { 106 | TodoConfigure( 107 | name = viewmodel.name, 108 | onNameChange = viewmodel::onNameChange, 109 | important = viewmodel.important, 110 | onImportantChange = viewmodel::onImportantChange 111 | ) 112 | Spacer( 113 | modifier = Modifier.height(10.dp) 114 | ) 115 | Text( 116 | text = stringResource(id = R.string.x_created_timestamp, viewmodel.timestamp), 117 | style = MaterialTheme.typography.bodyMedium, 118 | modifier = Modifier.padding(horizontal = 12.dp) 119 | ) 120 | } 121 | } 122 | } 123 | 124 | data class TodoEditScreenArgs( 125 | val todo: TodoModel 126 | ) -------------------------------------------------------------------------------- /app/src/main/java/xyz/teamgravity/todo/presentation/screen/support/SupportLandscapeScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.support 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.Spacer 8 | import androidx.compose.foundation.layout.WindowInsets 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.safeDrawing 13 | import androidx.compose.foundation.layout.width 14 | import androidx.compose.foundation.layout.windowInsetsPadding 15 | import androidx.compose.material.icons.Icons 16 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.material3.Scaffold 19 | import androidx.compose.material3.Text 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.ui.Alignment 22 | import androidx.compose.ui.Modifier 23 | import androidx.compose.ui.platform.LocalContext 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.text.font.FontWeight 26 | import androidx.compose.ui.text.style.TextAlign 27 | import androidx.compose.ui.unit.dp 28 | import androidx.compose.ui.unit.sp 29 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 30 | import xyz.teamgravity.todo.R 31 | import xyz.teamgravity.todo.core.util.Helper 32 | import xyz.teamgravity.todo.presentation.component.card.CardConnection 33 | import xyz.teamgravity.todo.presentation.theme.White 34 | 35 | @Composable 36 | fun SupportLandscapeScreen( 37 | onBackButtonClick: () -> Unit 38 | ) { 39 | val context = LocalContext.current 40 | 41 | Scaffold( 42 | topBar = { 43 | Row( 44 | verticalAlignment = Alignment.CenterVertically, 45 | modifier = Modifier 46 | .fillMaxWidth() 47 | .background(MaterialTheme.colorScheme.primary) 48 | .windowInsetsPadding(WindowInsets.safeDrawing) 49 | .padding(12.dp) 50 | ) { 51 | IconButtonPlain( 52 | onClick = onBackButtonClick, 53 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 54 | contentDescription = R.string.cd_back_button, 55 | tint = White 56 | ) 57 | Text( 58 | text = stringResource(id = R.string.need_help), 59 | textAlign = TextAlign.Center, 60 | fontSize = 18.sp, 61 | color = White, 62 | modifier = Modifier.weight(1F) 63 | ) 64 | Spacer( 65 | modifier = Modifier.width(38.dp) 66 | ) 67 | } 68 | }, 69 | contentWindowInsets = WindowInsets.safeDrawing 70 | ) { padding -> 71 | Column( 72 | modifier = Modifier 73 | .fillMaxSize() 74 | .padding(padding) 75 | .padding( 76 | horizontal = 16.dp, 77 | vertical = 20.dp 78 | ) 79 | ) { 80 | Column( 81 | verticalArrangement = Arrangement.SpaceEvenly, 82 | modifier = Modifier 83 | .fillMaxWidth() 84 | .weight(1F) 85 | ) { 86 | Text( 87 | text = stringResource(id = R.string.connect_us), 88 | textAlign = TextAlign.Center, 89 | fontSize = 24.sp, 90 | fontWeight = FontWeight.Bold, 91 | modifier = Modifier.fillMaxWidth() 92 | ) 93 | Text( 94 | text = stringResource(id = R.string.connect_us_body), 95 | textAlign = TextAlign.Center, 96 | style = MaterialTheme.typography.bodyMedium, 97 | modifier = Modifier.fillMaxWidth() 98 | ) 99 | } 100 | Row( 101 | horizontalArrangement = Arrangement.spacedBy(16.dp), 102 | verticalAlignment = Alignment.CenterVertically, 103 | modifier = Modifier 104 | .fillMaxWidth() 105 | .weight(1F) 106 | ) { 107 | CardConnection( 108 | onClick = { 109 | Helper.connectViaTelegram(context) 110 | }, 111 | icon = R.drawable.ic_telegram, 112 | title = R.string.via_telegram, 113 | contentDescription = R.string.cd_via_telegram, 114 | fillMaxSize = false, 115 | modifier = Modifier.weight(1F) 116 | ) 117 | CardConnection( 118 | onClick = { 119 | Helper.connectViaEmail(context) 120 | }, 121 | icon = R.drawable.ic_email, 122 | title = R.string.via_email, 123 | contentDescription = R.string.cd_via_email, 124 | fillMaxSize = false, 125 | modifier = Modifier.weight(1F) 126 | ) 127 | } 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

ToDo

2 | 3 |

4 | License 5 | API 6 |

7 | 8 |

9 | 🎯 "ToDo" app demonstrates modern Android app development with Jetpack Compose, Hilt, Material3, Coroutines, Flows, Room based on MVVM architecture. 10 |

11 |
12 | 13 |

14 | 15 |

16 | 17 | # Download 18 | 19 | You can download the release app on Google Play Store: 20 | 21 | 22 | Get it on Google Play 23 | 24 | 25 | # Tech stack 26 | 27 | - [Kotlin](https://kotlinlang.org/): first class programming language for native Android development. 28 | - [Kotlin Coroutines](https://github.com/Kotlin/kotlinx.coroutines): structured concurrency. 29 | - [Kotlin Flows](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/): reactive communication. 30 | - [Material3](https://m3.material.io/): modern UI/UX guidelines and components. 31 | - [Jetpack Compose](https://developer.android.com/jetpack/compose): modern, declarative way of building UI in Kotlin. 32 | - [Jetpack Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle): observe Android lifecycles and handle UI states upon the lifecycle changes. 33 | - [Jetpack ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel): manages UI-related data holder and lifecycle aware. Allows data to survive configuration changes such as screen rotations. 34 | - [Jetpack SavedStateHandle](https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate): in case of process death, key-value map that allows to write and retrieve bundle data to and from the saved state. 35 | - [Jetpack DataStore Preferences](https://developer.android.com/topic/libraries/architecture/datastore): uses Kotlin Coroutines and Flows to store data asynchronously, consistently, and transactionally. 36 | - [Room](https://developer.android.com/training/data-storage/room): SQLite abstraction and database solution. 37 | - [Dagger Hilt](https://dagger.dev/hilt/): first class dependency injection for native Android development. 38 | - [Firebase](https://firebase.google.com/): tracks analytics and crashes using the Firebase services. 39 | - [Compose Destinations](https://composedestinations.rafaelcosta.xyz/): a type-safe navigation for composables. 40 | - [Timber](https://github.com/JakeWharton/timber): a logger with a small, extensible API. 41 | 42 | # Architecture 43 | 44 | "ToDo" is based on the MVVM architecture pattern, Repository pattern, Mapper pattern. 45 | 46 | 47 | 48 | # MAD Score 49 | 50 |

51 | 52 | 53 |

54 | 55 | # About 56 | 57 | A beautifully simple ToDo application that emphasises simplicity and ease of use. Whether you want a shopping list, grocery list or you just have lots of things to remember ToDo is built for you. ToDo is fully offline as it does not require internet connection. Also, ToDo is very fast and very light! The size of APK is lighter than 2 MB. It also supports Dark Mode and multiple languages (English, Russian, Uzbek). 58 | 59 | Personally, I use ToDo app to save the urls of articles that I plan to read later. So, I can mark them as completed and everything is tracked also saved in the database. So, ToDo can be used in different situations. I hope it can solve your problem and make your life easier! 60 | 61 | # Features 62 | 63 | - Add, edit and delete tasks. 64 | - Undo deleted tasks. 65 | - Mark tasks as completed. Completed tasks have "Checked/Done" indicator and it is possible to hide them from list. 66 | - Mark tasks as "Important". Important tasks are listed first and they will have red "Warning" indicator. 67 | - Hide completed tasks from the list. 68 | - Sort tasks by name and by created date. 69 | - Your preferences are saved. 70 | - Search for your tasks easily. 71 | - Delete all tasks at once, delete all completed tasks at once, or delete a task by swiping it away. 72 | - Beautiful animations and indicators. 73 | - Dark/Light theme support. 74 | - Dynamic theme support. 75 | - Multiple language support. 76 | - Multiple screen sizes support. 77 | - Light that is less than 2 MB. 78 | - Works without the internet. 79 | - Simplicity that has only five screens. 80 | - Adaptive screens that change according to screen orientation. 81 | - Completely robust to process death. 82 | - Completely robust to configuration change. 83 | 84 | # License 85 | 86 | ```xml 87 | Designed and developed by raheemadamboev (Raheem) 2022. 88 | 89 | Licensed under the Apache License, Version 2.0 (the "License"); 90 | you may not use this file except in compliance with the License. 91 | You may obtain a copy of the License at 92 | 93 | http://www.apache.org/licenses/LICENSE-2.0 94 | 95 | Unless required by applicable law or agreed to in writing, software 96 | distributed under the License is distributed on an "AS IS" BASIS, 97 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 98 | See the License for the specific language governing permissions and 99 | limitations under the License. 100 | ``` 101 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | 38 | 39 | 41 | 42 | 154 | 155 | 158 | 159 | -------------------------------------------------------------------------------- /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/java/xyz/teamgravity/todo/presentation/screen/support/SupportPortraitScreen.kt: -------------------------------------------------------------------------------- 1 | package xyz.teamgravity.todo.presentation.screen.support 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.WindowInsets 10 | import androidx.compose.foundation.layout.fillMaxHeight 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.safeDrawing 16 | import androidx.compose.foundation.layout.statusBars 17 | import androidx.compose.foundation.layout.width 18 | import androidx.compose.foundation.layout.windowInsetsTopHeight 19 | import androidx.compose.material.icons.Icons 20 | import androidx.compose.material.icons.automirrored.rounded.ArrowBack 21 | import androidx.compose.material3.MaterialTheme 22 | import androidx.compose.material3.Scaffold 23 | import androidx.compose.material3.Text 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.ui.Alignment 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.layout.ContentScale 28 | import androidx.compose.ui.platform.LocalContext 29 | import androidx.compose.ui.res.painterResource 30 | import androidx.compose.ui.res.stringResource 31 | import androidx.compose.ui.text.font.FontWeight 32 | import androidx.compose.ui.text.style.TextAlign 33 | import androidx.compose.ui.unit.dp 34 | import androidx.compose.ui.unit.sp 35 | import xyz.teamgravity.coresdkcompose.button.IconButtonPlain 36 | import xyz.teamgravity.todo.R 37 | import xyz.teamgravity.todo.core.util.Helper 38 | import xyz.teamgravity.todo.presentation.component.card.CardConnection 39 | import xyz.teamgravity.todo.presentation.theme.White 40 | 41 | @Composable 42 | fun SupportPortraitScreen( 43 | onBackButtonClick: () -> Unit 44 | ) { 45 | val context = LocalContext.current 46 | 47 | Scaffold( 48 | topBar = { 49 | Column( 50 | modifier = Modifier 51 | .fillMaxWidth() 52 | .background(MaterialTheme.colorScheme.primary) 53 | ) { 54 | Spacer( 55 | modifier = Modifier.windowInsetsTopHeight(WindowInsets.statusBars) 56 | ) 57 | Row( 58 | verticalAlignment = Alignment.CenterVertically, 59 | modifier = Modifier 60 | .fillMaxWidth() 61 | .padding(12.dp) 62 | ) { 63 | IconButtonPlain( 64 | onClick = onBackButtonClick, 65 | icon = Icons.AutoMirrored.Rounded.ArrowBack, 66 | contentDescription = R.string.cd_back_button, 67 | tint = White 68 | ) 69 | Text( 70 | text = stringResource(id = R.string.need_help), 71 | textAlign = TextAlign.Center, 72 | fontSize = 18.sp, 73 | color = White, 74 | modifier = Modifier.weight(1F) 75 | ) 76 | Spacer( 77 | modifier = Modifier.width(38.dp) 78 | ) 79 | } 80 | Image( 81 | painter = painterResource(id = R.drawable.sticker_customer_service), 82 | contentDescription = stringResource(id = R.string.cd_user_support), 83 | contentScale = ContentScale.Fit, 84 | alignment = Alignment.Center, 85 | modifier = Modifier 86 | .fillMaxWidth() 87 | .fillMaxHeight(0.2F) 88 | ) 89 | } 90 | }, 91 | contentWindowInsets = WindowInsets.safeDrawing 92 | ) { padding -> 93 | Column( 94 | modifier = Modifier 95 | .fillMaxSize() 96 | .padding(padding) 97 | .padding(horizontal = 16.dp) 98 | ) { 99 | Column( 100 | verticalArrangement = Arrangement.SpaceEvenly, 101 | modifier = Modifier 102 | .fillMaxWidth() 103 | .weight(1F) 104 | ) { 105 | Text( 106 | text = stringResource(id = R.string.connect_us), 107 | textAlign = TextAlign.Center, 108 | fontSize = 24.sp, 109 | fontWeight = FontWeight.Bold, 110 | modifier = Modifier.fillMaxWidth() 111 | ) 112 | Text( 113 | text = stringResource(id = R.string.connect_us_body), 114 | textAlign = TextAlign.Center, 115 | style = MaterialTheme.typography.bodyMedium, 116 | modifier = Modifier.fillMaxWidth() 117 | ) 118 | } 119 | CardConnection( 120 | onClick = { 121 | Helper.connectViaTelegram(context) 122 | }, 123 | icon = R.drawable.ic_telegram, 124 | title = R.string.via_telegram, 125 | contentDescription = R.string.cd_via_telegram, 126 | fillMaxSize = true, 127 | modifier = Modifier 128 | .fillMaxWidth() 129 | .weight(1F) 130 | ) 131 | Spacer( 132 | modifier = Modifier.height(20.dp) 133 | ) 134 | CardConnection( 135 | onClick = { 136 | Helper.connectViaEmail(context) 137 | }, 138 | icon = R.drawable.ic_email, 139 | title = R.string.via_email, 140 | contentDescription = R.string.cd_via_email, 141 | fillMaxSize = true, 142 | modifier = Modifier 143 | .fillMaxWidth() 144 | .weight(1F) 145 | ) 146 | Spacer( 147 | modifier = Modifier.height(20.dp) 148 | ) 149 | } 150 | } 151 | } --------------------------------------------------------------------------------