├── .github └── workflows │ └── github-repo-stats.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── CHANGELOG.md ├── LICENSE ├── PRIVACY_POLICY.md ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ └── com.vishal2376.snaptick.data.local.TaskDatabase │ │ ├── 1.json │ │ └── 2.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── vishal2376 │ │ └── snaptick │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── vishal2376 │ │ │ └── snaptick │ │ │ ├── MainActivity.kt │ │ │ ├── SnaptickApplication.kt │ │ │ ├── data │ │ │ ├── local │ │ │ │ ├── Migration.kt │ │ │ │ ├── TaskDao.kt │ │ │ │ └── TaskDatabase.kt │ │ │ └── repositories │ │ │ │ └── TaskRepository.kt │ │ │ ├── di │ │ │ └── AppModule.kt │ │ │ ├── domain │ │ │ ├── converters │ │ │ │ ├── LocalDateConverter.kt │ │ │ │ └── LocalTimeConverter.kt │ │ │ ├── interactor │ │ │ │ └── AppWidgetInteractor.kt │ │ │ └── model │ │ │ │ ├── BackupData.kt │ │ │ │ └── Task.kt │ │ │ ├── presentation │ │ │ ├── about_screen │ │ │ │ ├── AboutScreen.kt │ │ │ │ └── component │ │ │ │ │ └── FeatureComponent.kt │ │ │ ├── add_edit_screen │ │ │ │ ├── AddEditScreenEvent.kt │ │ │ │ ├── AddTaskScreen.kt │ │ │ │ ├── EditTaskScreen.kt │ │ │ │ └── components │ │ │ │ │ ├── ConfirmDeleteDialog.kt │ │ │ │ │ ├── CustomDatePickerDialog.kt │ │ │ │ │ ├── CustomDurationDialogComponent.kt │ │ │ │ │ ├── DurationComponent.kt │ │ │ │ │ ├── PriorityComponent.kt │ │ │ │ │ ├── ShowNativeTimePickerComponent.kt │ │ │ │ │ └── WeekDaysComponent.kt │ │ │ ├── calender_screen │ │ │ │ ├── CalenderScreen.kt │ │ │ │ └── component │ │ │ │ │ ├── MonthDayComponent.kt │ │ │ │ │ └── WeekDayComponent.kt │ │ │ ├── common │ │ │ │ ├── AppTheme.kt │ │ │ │ ├── CalenderView.kt │ │ │ │ ├── CustomSnackBar.kt │ │ │ │ ├── FilterTasksUtils.kt │ │ │ │ ├── NativeTimePickerDialog.kt │ │ │ │ ├── NavDrawerItem.kt │ │ │ │ ├── NotificationPermissionHandler.kt │ │ │ │ ├── Priority.kt │ │ │ │ ├── ShowTimePicker.kt │ │ │ │ ├── SortTask.kt │ │ │ │ ├── SwipeActionBox.kt │ │ │ │ └── TextStyles.kt │ │ │ ├── completed_task_screen │ │ │ │ └── CompletedTaskScreen.kt │ │ │ ├── free_time_screen │ │ │ │ ├── FreeTimeScreen.kt │ │ │ │ └── components │ │ │ │ │ ├── CustomPieChart.kt │ │ │ │ │ └── PieChartItemComponent.kt │ │ │ ├── home_screen │ │ │ │ ├── HomeScreen.kt │ │ │ │ ├── HomeScreenEvent.kt │ │ │ │ └── components │ │ │ │ │ ├── EmptyTaskComponent.kt │ │ │ │ │ ├── InfoComponent.kt │ │ │ │ │ ├── NavigationDrawerComponent.kt │ │ │ │ │ ├── SortTaskDialogComponent.kt │ │ │ │ │ ├── TaskComponent.kt │ │ │ │ │ └── WhatsNewDialogComponent.kt │ │ │ ├── main │ │ │ │ ├── MainEvent.kt │ │ │ │ └── MainState.kt │ │ │ ├── navigation │ │ │ │ ├── AppNavigation.kt │ │ │ │ └── Routes.kt │ │ │ ├── pomodoro_screen │ │ │ │ ├── PomodoroScreen.kt │ │ │ │ ├── PomodoroScreenEvent.kt │ │ │ │ └── components │ │ │ │ │ └── CustomCircularProgressBar.kt │ │ │ ├── settings │ │ │ │ ├── SettingsScreen.kt │ │ │ │ ├── common │ │ │ │ │ ├── SettingCategoryItem.kt │ │ │ │ │ ├── ToggleOption.kt │ │ │ │ │ └── TopLanguage.kt │ │ │ │ └── components │ │ │ │ │ ├── LanguageOptionComponent.kt │ │ │ │ │ ├── SettingCategoryComponent.kt │ │ │ │ │ ├── SleepTimeOptionComponent.kt │ │ │ │ │ ├── SwipeActionOptionComponent.kt │ │ │ │ │ ├── ThemeOptionComponent.kt │ │ │ │ │ └── TimePickerOptionComponent.kt │ │ │ ├── this_week_task_screen │ │ │ │ └── ThisWeekTaskScreen.kt │ │ │ └── viewmodels │ │ │ │ └── TaskViewModel.kt │ │ │ ├── ui │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ ├── util │ │ │ ├── AudioUtil.kt │ │ │ ├── BackupManager.kt │ │ │ ├── Constants.kt │ │ │ ├── DummyTasks.kt │ │ │ ├── GsonAdapters.kt │ │ │ ├── LocaleHelper.kt │ │ │ ├── NotificationHelper.kt │ │ │ ├── SettingsStore.kt │ │ │ ├── Utils.kt │ │ │ └── Validation.kt │ │ │ ├── widget │ │ │ ├── OnTaskClickedCallback.kt │ │ │ ├── SnaptickWidget.kt │ │ │ ├── SnaptickWidgetReceiver.kt │ │ │ ├── SnaptickWidgetState.kt │ │ │ ├── SnaptickWidgetStateDefinition.kt │ │ │ ├── components │ │ │ │ ├── SnaptickWidgetTheme.kt │ │ │ │ ├── WidgetTaskComponent.kt │ │ │ │ ├── WidgetTasks.kt │ │ │ │ └── WidgetsNoTasks.kt │ │ │ ├── di │ │ │ │ └── WidgetModule.kt │ │ │ ├── interactor │ │ │ │ └── AppWidgetInteractorImpl.kt │ │ │ ├── model │ │ │ │ ├── TaskToWidgetTaskMapper.kt │ │ │ │ └── WidgetTaskModel.kt │ │ │ ├── util │ │ │ │ └── LocalTimeGsonSerializer.kt │ │ │ └── worker │ │ │ │ ├── WidgetTaskUpdateDataWorker.kt │ │ │ │ └── WorkerConstants.kt │ │ │ └── worker │ │ │ ├── NotificationWorker.kt │ │ │ └── RepeatTaskWorker.kt │ └── res │ │ ├── drawable │ │ ├── app_logo.xml │ │ ├── app_logo_amoled.xml │ │ ├── app_widget_preview.xml │ │ ├── bg_round_primary.xml │ │ ├── bg_round_secondary.xml │ │ ├── bg_task_high.xml │ │ ├── bg_task_low.xml │ │ ├── bg_task_med.xml │ │ ├── delete_task.xml │ │ ├── ic_check_circle.xml │ │ ├── ic_clock.xml │ │ ├── ic_code.xml │ │ ├── ic_fire.xml │ │ ├── ic_github.xml │ │ ├── ic_info.xml │ │ ├── ic_instagram.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_linkedin.xml │ │ ├── ic_moon.xml │ │ ├── ic_support.xml │ │ ├── ic_swipe_left.xml │ │ ├── ic_task_list.xml │ │ ├── ic_theme.xml │ │ ├── ic_timer.xml │ │ ├── ic_translate.xml │ │ ├── ic_twitter.xml │ │ ├── ic_uncheck_circle.xml │ │ ├── monochrome_icon.xml │ │ ├── no_tasks.xml │ │ ├── splash_app_logo.xml │ │ └── splash_app_logo_anim.xml │ │ ├── font │ │ ├── montserrat.ttf │ │ ├── roboto.ttf │ │ ├── roboto_mono.ttf │ │ └── roboto_mono_thin.ttf │ │ ├── layout │ │ ├── widget_loading.xml │ │ └── widget_preview.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── raw │ │ ├── task_added.mp3 │ │ ├── task_completed.mp3 │ │ └── task_deleted.mp3 │ │ ├── values-da │ │ └── strings.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-en │ │ └── strings.xml │ │ ├── values-es │ │ └── strings.xml │ │ ├── values-fa │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-nl │ │ └── strings.xml │ │ ├── values-no │ │ └── strings.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-pt │ │ └── strings.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-tr │ │ └── strings.xml │ │ ├── values-uk │ │ └── strings.xml │ │ ├── values-v31 │ │ ├── strings.xml │ │ └── themes.xml │ │ ├── values-vi │ │ └── strings.xml │ │ ├── values-zh │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ ├── xml-v31 │ │ └── snaptick_widget_info.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── snaptick_widget_info.xml │ └── test │ └── java │ └── com │ └── vishal2376 │ └── snaptick │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── crowdin.yml ├── fastlane └── metadata │ └── android │ └── en-US │ ├── full_description.txt │ ├── images │ ├── featureGraphic.png │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ └── short_description.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/workflows/github-repo-stats.yml: -------------------------------------------------------------------------------- 1 | name: github-repo-stats 2 | 3 | on: 4 | schedule: 5 | # Run this once per day, towards the end of the day for keeping the most 6 | # recent data point most meaningful (hours are interpreted in UTC). 7 | - cron: "0 23 * * *" 8 | workflow_dispatch: # Allow for running this manually. 9 | 10 | jobs: 11 | j1: 12 | name: github-repo-stats 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: run-ghrs 16 | # Use latest release. 17 | uses: jgehrcke/github-repo-stats@RELEASE 18 | with: 19 | ghtoken: ${{ secrets.ghrs_github_api_token }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | release/ 17 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Snaptick -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | **Privacy Policy** 2 | 3 | This privacy policy applies to the Snaptick app (hereby referred to as "Application") for mobile devices that was created by ECHOO (hereby referred to as "Service Provider") as an Open Source service. This service is intended for use "AS IS". 4 | 5 | **What information does the Application obtain and how is it used?** 6 | 7 | The Application does not obtain any information when you download and use it. Registration is not required to use the Application. 8 | 9 | **Does the Application collect precise real time location information of the device?** 10 | 11 | This Application does not collect precise information about the location of your mobile device. 12 | 13 | **Do third parties see and/or have access to information obtained by the Application?** 14 | 15 | Since the Application does not collect any information, no data is shared with third parties. 16 | 17 | **What are my opt-out rights?** 18 | 19 | You can stop all collection of information by the Application easily by uninstalling it. You may use the standard uninstall processes as may be available as part of your mobile device or via the mobile application marketplace or network. 20 | 21 | **Children** 22 | 23 | The Application is not used to knowingly solicit data from or market to children under the age of 13. 24 | 25 | The Service Provider does not knowingly collect personally identifiable information from children. The Service Provider encourages all children to never submit any personally identifiable information through the Application and/or Services. The Service Provider encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child has provided personally identifiable information to the Service Provider through the Application and/or Services, please contact the Service Provider (vishalsingh2376@gmail.com) so that they will be able to take the necessary actions. You must also be at least 16 years of age to consent to the processing of your personally identifiable information in your country (in some countries we may allow your parent or guardian to do so on your behalf). 26 | 27 | **Security** 28 | 29 | The Service Provider is concerned about safeguarding the confidentiality of your information. However, since the Application does not collect any information, there is no risk of your data being accessed by unauthorized individuals. 30 | 31 | **Changes** 32 | 33 | This Privacy Policy may be updated from time to time for any reason. The Service Provider will notify you of any changes to their Privacy Policy by updating this page with the new Privacy Policy. You are advised to consult this Privacy Policy regularly for any changes, as continued use is deemed approval of all changes. 34 | 35 | This privacy policy is effective as of 2024-12-02 36 | 37 | **Your Consent** 38 | 39 | By using the Application, you are consenting to the processing of your information as set forth in this Privacy Policy now and as amended by the Service Provider. 40 | 41 | **Contact Us** 42 | 43 | If you have any questions regarding privacy while using the Application, or have questions about the practices, please contact the Service Provider via email at vishalsingh2376@gmail.com. 44 | 45 | * * * 46 | 47 | This privacy policy page was generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.nisrulz.com/) 48 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/schemas/com.vishal2376.snaptick.data.local.TaskDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "64d60288e03170f0936cb69fb4bcd21c", 6 | "entities": [ 7 | { 8 | "tableName": "task_table", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT NOT NULL, `title` TEXT NOT NULL, `isCompleted` INTEGER NOT NULL, `startTime` TEXT NOT NULL, `endTime` TEXT NOT NULL, `reminder` INTEGER NOT NULL, `isRepeat` INTEGER NOT NULL, `repeatWeekdays` TEXT NOT NULL, `pomodoroTimer` INTEGER NOT NULL, `date` TEXT NOT NULL, `priority` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "uuid", 19 | "columnName": "uuid", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "title", 25 | "columnName": "title", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "isCompleted", 31 | "columnName": "isCompleted", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "startTime", 37 | "columnName": "startTime", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | }, 41 | { 42 | "fieldPath": "endTime", 43 | "columnName": "endTime", 44 | "affinity": "TEXT", 45 | "notNull": true 46 | }, 47 | { 48 | "fieldPath": "reminder", 49 | "columnName": "reminder", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "isRepeat", 55 | "columnName": "isRepeat", 56 | "affinity": "INTEGER", 57 | "notNull": true 58 | }, 59 | { 60 | "fieldPath": "repeatWeekdays", 61 | "columnName": "repeatWeekdays", 62 | "affinity": "TEXT", 63 | "notNull": true 64 | }, 65 | { 66 | "fieldPath": "pomodoroTimer", 67 | "columnName": "pomodoroTimer", 68 | "affinity": "INTEGER", 69 | "notNull": true 70 | }, 71 | { 72 | "fieldPath": "date", 73 | "columnName": "date", 74 | "affinity": "TEXT", 75 | "notNull": true 76 | }, 77 | { 78 | "fieldPath": "priority", 79 | "columnName": "priority", 80 | "affinity": "INTEGER", 81 | "notNull": true 82 | } 83 | ], 84 | "primaryKey": { 85 | "autoGenerate": true, 86 | "columnNames": [ 87 | "id" 88 | ] 89 | }, 90 | "indices": [], 91 | "foreignKeys": [] 92 | } 93 | ], 94 | "views": [], 95 | "setupQueries": [ 96 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 97 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '64d60288e03170f0936cb69fb4bcd21c')" 98 | ] 99 | } 100 | } -------------------------------------------------------------------------------- /app/schemas/com.vishal2376.snaptick.data.local.TaskDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "69357131b76497f78d56422dbaf1b9b4", 6 | "entities": [ 7 | { 8 | "tableName": "task_table", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT NOT NULL, `title` TEXT NOT NULL, `isCompleted` INTEGER NOT NULL, `startTime` TEXT NOT NULL, `endTime` TEXT NOT NULL, `reminder` INTEGER NOT NULL, `isRepeated` INTEGER NOT NULL, `repeatWeekdays` TEXT NOT NULL, `pomodoroTimer` INTEGER NOT NULL, `date` TEXT NOT NULL, `priority` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "uuid", 19 | "columnName": "uuid", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "title", 25 | "columnName": "title", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "isCompleted", 31 | "columnName": "isCompleted", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "startTime", 37 | "columnName": "startTime", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | }, 41 | { 42 | "fieldPath": "endTime", 43 | "columnName": "endTime", 44 | "affinity": "TEXT", 45 | "notNull": true 46 | }, 47 | { 48 | "fieldPath": "reminder", 49 | "columnName": "reminder", 50 | "affinity": "INTEGER", 51 | "notNull": true 52 | }, 53 | { 54 | "fieldPath": "isRepeated", 55 | "columnName": "isRepeated", 56 | "affinity": "INTEGER", 57 | "notNull": true 58 | }, 59 | { 60 | "fieldPath": "repeatWeekdays", 61 | "columnName": "repeatWeekdays", 62 | "affinity": "TEXT", 63 | "notNull": true 64 | }, 65 | { 66 | "fieldPath": "pomodoroTimer", 67 | "columnName": "pomodoroTimer", 68 | "affinity": "INTEGER", 69 | "notNull": true 70 | }, 71 | { 72 | "fieldPath": "date", 73 | "columnName": "date", 74 | "affinity": "TEXT", 75 | "notNull": true 76 | }, 77 | { 78 | "fieldPath": "priority", 79 | "columnName": "priority", 80 | "affinity": "INTEGER", 81 | "notNull": true 82 | } 83 | ], 84 | "primaryKey": { 85 | "autoGenerate": true, 86 | "columnNames": [ 87 | "id" 88 | ] 89 | }, 90 | "indices": [], 91 | "foreignKeys": [] 92 | } 93 | ], 94 | "views": [], 95 | "setupQueries": [ 96 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 97 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '69357131b76497f78d56422dbaf1b9b4')" 98 | ] 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/vishal2376/snaptick/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.vishal2376.snaptick", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | import androidx.activity.enableEdgeToEdge 8 | import androidx.activity.result.ActivityResultLauncher 9 | import androidx.activity.result.contract.ActivityResultContracts 10 | import androidx.activity.viewModels 11 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 12 | import com.vishal2376.snaptick.presentation.common.CustomSnackBar 13 | import com.vishal2376.snaptick.presentation.navigation.AppNavigation 14 | import com.vishal2376.snaptick.presentation.viewmodels.TaskViewModel 15 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 16 | import com.vishal2376.snaptick.util.NotificationHelper 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | @AndroidEntryPoint 20 | class MainActivity : ComponentActivity() { 21 | 22 | private val taskViewModel by viewModels() 23 | private lateinit var notificationHelper: NotificationHelper 24 | lateinit var backupPickerLauncher: ActivityResultLauncher 25 | lateinit var restorePickerLauncher: ActivityResultLauncher 26 | 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | // init splash screen 30 | installSplashScreen() 31 | 32 | super.onCreate(savedInstanceState) 33 | enableEdgeToEdge() 34 | 35 | // create notification channel 36 | notificationHelper = NotificationHelper(applicationContext) 37 | notificationHelper.createNotificationChannel() 38 | 39 | // load app state 40 | taskViewModel.loadAppState(applicationContext) 41 | 42 | // Launchers for backup and restore 43 | backupPickerLauncher = 44 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 45 | if (result.resultCode == RESULT_OK) { 46 | result.data?.data?.let { uri -> 47 | taskViewModel.createBackup( 48 | uri, 49 | taskViewModel.backupData.value, 50 | applicationContext 51 | ) 52 | } 53 | } 54 | } 55 | 56 | restorePickerLauncher = 57 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 58 | if (result.resultCode == RESULT_OK) { 59 | result.data?.data?.let { uri -> 60 | taskViewModel.loadBackup(uri, applicationContext) 61 | } 62 | } 63 | } 64 | 65 | setContent { 66 | SnaptickTheme( 67 | theme = taskViewModel.appState.theme, 68 | dynamicColor = taskViewModel.appState.dynamicTheme 69 | ) { 70 | AppNavigation(taskViewModel = taskViewModel) 71 | CustomSnackBar() 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/SnaptickApplication.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.util.Log 6 | import androidx.hilt.work.HiltWorkerFactory 7 | import androidx.work.Configuration 8 | import androidx.work.ExistingPeriodicWorkPolicy 9 | import androidx.work.PeriodicWorkRequest 10 | import androidx.work.WorkManager 11 | import com.vishal2376.snaptick.util.Constants 12 | import com.vishal2376.snaptick.worker.RepeatTaskWorker 13 | import dagger.hilt.android.HiltAndroidApp 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.asExecutor 16 | import org.acra.ACRA 17 | import org.acra.BuildConfig 18 | import org.acra.config.CoreConfigurationBuilder 19 | import org.acra.config.DialogConfigurationBuilder 20 | import org.acra.config.MailSenderConfigurationBuilder 21 | import org.acra.data.StringFormat 22 | import java.time.LocalTime 23 | import java.util.concurrent.TimeUnit 24 | import javax.inject.Inject 25 | 26 | @HiltAndroidApp 27 | class SnaptickApplication : Application(), Configuration.Provider { 28 | 29 | @Inject 30 | lateinit var workerFactory: HiltWorkerFactory 31 | 32 | override val workManagerConfiguration: Configuration 33 | get() = Configuration.Builder() 34 | .setWorkerFactory(workerFactory) 35 | .setMinimumLoggingLevel(Log.INFO) 36 | .setExecutor(Dispatchers.Default.asExecutor()) 37 | .build() 38 | 39 | 40 | override fun attachBaseContext(base: Context?) { 41 | super.attachBaseContext(base) 42 | ACRA.init( 43 | this, CoreConfigurationBuilder() 44 | .withBuildConfigClass(BuildConfig::class.java) 45 | .withReportFormat(StringFormat.JSON) 46 | .withPluginConfigurations( 47 | 48 | // Dialog configuration: 49 | DialogConfigurationBuilder() 50 | .withText(getString(R.string.dialog_text)) 51 | .withTitle(getString(R.string.dialog_title)) 52 | .withPositiveButtonText(getString(R.string.dialog_positive)) 53 | .withNegativeButtonText(getString(R.string.dialog_negative)) 54 | .build(), 55 | 56 | // Mail sender configuration: 57 | MailSenderConfigurationBuilder() 58 | .withMailTo(Constants.EMAIL) 59 | .withReportFileName("crash_report.txt") 60 | .withReportAsFile(true) 61 | .build() 62 | ) 63 | ) 64 | } 65 | 66 | override fun onCreate() { 67 | super.onCreate() 68 | 69 | initWorker() 70 | } 71 | 72 | private fun initWorker() { 73 | val maxTimeSec = LocalTime.MAX.toSecondOfDay() + 1 74 | val currentTimeSec = LocalTime.now().toSecondOfDay() 75 | 76 | val delay = (maxTimeSec - currentTimeSec) 77 | if (delay > 0) { 78 | startRepeatWorker(delay) 79 | } 80 | } 81 | 82 | private fun startRepeatWorker(delay: Int) { 83 | // repeat task request 84 | val workRequest = 85 | PeriodicWorkRequest.Builder(RepeatTaskWorker::class.java, 1, TimeUnit.DAYS) 86 | .setInitialDelay(delay.toLong(), TimeUnit.SECONDS) 87 | .build() 88 | 89 | WorkManager.getInstance(applicationContext) 90 | .enqueueUniquePeriodicWork( 91 | "Repeat-Tasks", 92 | ExistingPeriodicWorkPolicy.KEEP, 93 | workRequest 94 | ) 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/data/local/Migration.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.data.local 2 | 3 | import androidx.room.migration.Migration 4 | import androidx.sqlite.db.SupportSQLiteDatabase 5 | 6 | val MIGRATION_1_2 = object : Migration(1, 2) { 7 | override fun migrate(db: SupportSQLiteDatabase) { 8 | // Add new columns 9 | db.execSQL("ALTER TABLE task_table ADD COLUMN repeatWeekdays TEXT NOT NULL DEFAULT ''") 10 | db.execSQL("ALTER TABLE task_table ADD COLUMN pomodoroTimer INTEGER NOT NULL DEFAULT -1") 11 | 12 | // Convert existing data for the new columns 13 | db.execSQL("UPDATE task_table SET repeatWeekdays = ''") 14 | db.execSQL("UPDATE task_table SET pomodoroTimer = -1") 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/data/local/TaskDao.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.data.local 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import androidx.room.Update 9 | import com.vishal2376.snaptick.domain.model.Task 10 | import kotlinx.coroutines.flow.Flow 11 | 12 | @Dao 13 | interface TaskDao { 14 | @Insert(onConflict = OnConflictStrategy.REPLACE) 15 | suspend fun insertTask(task: Task) 16 | 17 | @Delete 18 | suspend fun deleteTask(task: Task) 19 | 20 | @Update(onConflict = OnConflictStrategy.REPLACE) 21 | suspend fun updateTask(task: Task) 22 | 23 | @Query("SELECT * FROM task_table WHERE id=:id") 24 | suspend fun getTaskById(id: Int): Task 25 | 26 | @Query("SELECT * FROM task_table") 27 | fun getAllTasks(): Flow> 28 | 29 | @Query("SELECT * FROM task_table WHERE date = :selectedDate") 30 | fun getTasksByDate(selectedDate: String): Flow> 31 | 32 | @Query("SELECT * FROM task_table WHERE isRepeated = 1 AND date < :today") 33 | fun getLastRepeatedTasks(today: String): List 34 | 35 | @Query("DELETE FROM task_table") 36 | suspend fun deleteAllTasks() 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/data/local/TaskDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.data.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.vishal2376.snaptick.domain.model.Task 6 | 7 | @Database( 8 | entities = [Task::class], 9 | version = 2, 10 | ) 11 | abstract class TaskDatabase : RoomDatabase() { 12 | abstract fun taskDao(): TaskDao 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/data/repositories/TaskRepository.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.data.repositories 2 | 3 | import com.vishal2376.snaptick.data.local.TaskDao 4 | import com.vishal2376.snaptick.domain.interactor.AppWidgetInteractor 5 | import com.vishal2376.snaptick.domain.model.Task 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.onEach 8 | import java.time.LocalDate 9 | 10 | class TaskRepository( 11 | private val dao: TaskDao, 12 | private val interactor: AppWidgetInteractor 13 | ) { 14 | suspend fun insertTask(task: Task) { 15 | dao.insertTask(task) 16 | } 17 | 18 | suspend fun deleteTask(task: Task) { 19 | dao.deleteTask(task) 20 | } 21 | 22 | suspend fun updateTask(task: Task) { 23 | dao.updateTask(task) 24 | } 25 | 26 | suspend fun getTaskById(id: Int): Task? { 27 | return dao.getTaskById(id) 28 | } 29 | 30 | suspend fun deleteAllTasks() { 31 | dao.deleteAllTasks() 32 | } 33 | 34 | fun getTasksByDate(selectedDate: LocalDate): Flow> { 35 | return dao.getTasksByDate(selectedDate.toString()) 36 | } 37 | 38 | fun getTodayTasks(): Flow> { 39 | return dao.getTasksByDate(LocalDate.now().toString()) 40 | } 41 | 42 | fun getLastRepeatedTasks(): List { 43 | val today = LocalDate.now().toString() 44 | return dao.getLastRepeatedTasks(today) 45 | } 46 | 47 | fun getAllTasks(): Flow> { 48 | return dao.getAllTasks().onEach { 49 | //on each emit enqueue the worker 50 | // thus widget get updated on any of CRUD operation 51 | interactor.enqueueWidgetDataWorker() 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.vishal2376.snaptick.data.local.MIGRATION_1_2 6 | import com.vishal2376.snaptick.data.local.TaskDao 7 | import com.vishal2376.snaptick.data.local.TaskDatabase 8 | import com.vishal2376.snaptick.data.repositories.TaskRepository 9 | import com.vishal2376.snaptick.domain.interactor.AppWidgetInteractor 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.android.qualifiers.ApplicationContext 14 | import dagger.hilt.components.SingletonComponent 15 | import javax.inject.Singleton 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | object AppModule { 20 | 21 | @Provides 22 | @Singleton 23 | fun providesLocalDatabase(@ApplicationContext context: Context): TaskDatabase { 24 | return Room.databaseBuilder(context, TaskDatabase::class.java, "local_db") 25 | .fallbackToDestructiveMigration() 26 | .addMigrations(MIGRATION_1_2) 27 | .build() 28 | } 29 | 30 | @Provides 31 | @Singleton 32 | fun providesTaskDao(db: TaskDatabase): TaskDao { 33 | return db.taskDao() 34 | } 35 | 36 | @Provides 37 | @Singleton 38 | fun providesTaskRepository( 39 | dao: TaskDao, 40 | widgetInteract: AppWidgetInteractor 41 | ): TaskRepository { 42 | return TaskRepository(dao, widgetInteract) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/domain/converters/LocalDateConverter.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.domain.converters 2 | 3 | import androidx.room.TypeConverter 4 | import java.time.LocalDate 5 | import java.time.format.DateTimeFormatter 6 | 7 | object LocalDateConverter { 8 | 9 | @TypeConverter 10 | @JvmStatic 11 | fun fromString(value: String?): LocalDate? { 12 | return value?.let { LocalDate.parse(it) } 13 | } 14 | 15 | @TypeConverter 16 | @JvmStatic 17 | fun toString(value: LocalDate?): String? { 18 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") 19 | return value?.format(formatter) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/domain/converters/LocalTimeConverter.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.domain.converters 2 | 3 | import androidx.room.TypeConverter 4 | import java.time.LocalTime 5 | 6 | object LocalTimeConverter { 7 | @TypeConverter 8 | @JvmStatic 9 | fun fromString(value: String?): LocalTime? { 10 | return value?.let { LocalTime.parse(it) } 11 | } 12 | 13 | @TypeConverter 14 | @JvmStatic 15 | fun toString(localTime: LocalTime?): String? { 16 | return localTime?.toString() 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/domain/interactor/AppWidgetInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.domain.interactor 2 | 3 | interface AppWidgetInteractor { 4 | 5 | /** 6 | * This worker is mainly used for updates in the widget, 7 | * Updated may include completing a tasks also adding a new one 8 | */ 9 | fun enqueueWidgetDataWorker() 10 | 11 | /** 12 | * Periodic Worker which runs daily at midnight updating the task, 13 | * This worker is to be enqueued when the widget is enabled 14 | */ 15 | fun enqueuePeriodicWidgetUpdateWorker() 16 | 17 | /** 18 | * Periodic Worker should be cancelled when the widget is disabled 19 | */ 20 | fun cancelPeriodicWidgetUpdateWorker() 21 | 22 | fun cancelWidgetDateWorker() 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/domain/model/BackupData.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.domain.model 2 | 3 | data class BackupData( 4 | val tasks: List = emptyList() 5 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/about_screen/component/FeatureComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.about_screen.component 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.width 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.verticalScroll 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.unit.dp 18 | import com.vishal2376.snaptick.R 19 | import com.vishal2376.snaptick.presentation.common.h3TextStyle 20 | import com.vishal2376.snaptick.presentation.common.infoTextStyle 21 | 22 | @Composable 23 | fun FeaturesComponent(modifier: Modifier = Modifier) { 24 | Column( 25 | modifier = modifier 26 | .padding(32.dp, 0.dp) 27 | .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(8.dp) 28 | ) { 29 | FeatureItem(icon = "📝", text = stringResource(R.string.create_and_edit_tasks)) 30 | FeatureItem(icon = "⏲️", text = stringResource(R.string.pomodoro_timer)) 31 | FeatureItem(icon = "🔄", text = stringResource(R.string.sort_tasks)) 32 | FeatureItem(icon = "💾", text = stringResource(R.string.backup_restore_data)) 33 | FeatureItem(icon = "⏰", text = stringResource(R.string.analyze_free_time)) 34 | FeatureItem(icon = "😴", text = stringResource(R.string.set_sleep_time)) 35 | FeatureItem(icon = "🗓️", text = stringResource(R.string.manage_tasks_in_calendar_view)) 36 | FeatureItem(icon = "🔁", text = stringResource(R.string.repeatable_tasks_with_notification)) 37 | FeatureItem(icon = "🎬", text = stringResource(R.string.smooth_animations)) 38 | FeatureItem(icon = "🎨", text = stringResource(R.string.modern_ui_with_cool_themes)) 39 | FeatureItem(icon = "🌐", text = stringResource(R.string.available_in_15_languages)) 40 | FeatureItem(icon = "🧩", text = stringResource(R.string.create_widgets)) 41 | } 42 | } 43 | 44 | @Composable 45 | fun FeatureItem(icon: String, text: String) { 46 | Row(verticalAlignment = Alignment.CenterVertically) { 47 | Text(text = icon, style = infoTextStyle, color = MaterialTheme.colorScheme.onBackground) 48 | Spacer(modifier = Modifier.width(8.dp)) 49 | Text(text = text, style = h3TextStyle, color = MaterialTheme.colorScheme.onBackground) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/add_edit_screen/AddEditScreenEvent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.add_edit_screen 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | import com.vishal2376.snaptick.presentation.common.Priority 5 | import java.time.LocalTime 6 | 7 | sealed class AddEditScreenEvent { 8 | data class OnAddTaskClick(val task: Task) : AddEditScreenEvent() 9 | data class OnDeleteTaskClick(val task: Task) : AddEditScreenEvent() 10 | data class OnUpdateTitle(val title: String) : AddEditScreenEvent() 11 | data class OnUpdateStartTime(val time: LocalTime) : AddEditScreenEvent() 12 | data class OnUpdateEndTime(val time: LocalTime) : AddEditScreenEvent() 13 | data class OnUpdateReminder(val reminder: Boolean) : AddEditScreenEvent() 14 | data object ResetPomodoroTimer : AddEditScreenEvent() 15 | data class OnUpdateIsRepeated(val isRepeated: Boolean) : AddEditScreenEvent() 16 | data class OnUpdateRepeatWeekDays(val weekDays: String) : AddEditScreenEvent() 17 | data class OnUpdatePriority(val priority: Priority) : AddEditScreenEvent() 18 | data object OnUpdateTask : AddEditScreenEvent() 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/add_edit_screen/components/ConfirmDeleteDialog.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.add_edit_screen.components 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.border 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.layout.size 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material3.Button 14 | import androidx.compose.material3.ButtonDefaults 15 | import androidx.compose.material3.Card 16 | import androidx.compose.material3.CardDefaults 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.material3.Text 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.res.painterResource 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.tooling.preview.Preview 26 | import androidx.compose.ui.unit.dp 27 | import androidx.compose.ui.window.Dialog 28 | import com.vishal2376.snaptick.R 29 | import com.vishal2376.snaptick.presentation.common.h2TextStyle 30 | import com.vishal2376.snaptick.ui.theme.LightGray 31 | import com.vishal2376.snaptick.ui.theme.Red 32 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 33 | 34 | @Composable 35 | fun ConfirmDeleteDialog(onClose: () -> Unit, onDelete: () -> Unit) { 36 | Dialog(onDismissRequest = { onClose() }) { 37 | Card( 38 | modifier = Modifier 39 | .fillMaxWidth() 40 | .border( 41 | 4.dp, 42 | MaterialTheme.colorScheme.primaryContainer, 43 | RoundedCornerShape(16.dp) 44 | ), 45 | shape = RoundedCornerShape(16.dp), 46 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.background) 47 | ) { 48 | Column( 49 | modifier = Modifier 50 | .fillMaxWidth() 51 | .padding( 52 | 32.dp 53 | ), 54 | horizontalAlignment = Alignment.CenterHorizontally, 55 | verticalArrangement = Arrangement.spacedBy(16.dp) 56 | ) { 57 | Text( 58 | text = stringResource(R.string.delete_task), 59 | color = MaterialTheme.colorScheme.onBackground, 60 | style = h2TextStyle 61 | ) 62 | Image( 63 | painter = painterResource(id = R.drawable.delete_task), 64 | contentDescription = null, 65 | Modifier.size(150.dp) 66 | ) 67 | 68 | Row( 69 | modifier = Modifier.fillMaxWidth(), 70 | horizontalArrangement = Arrangement.SpaceBetween 71 | ) { 72 | Button( 73 | onClick = { onClose() }, 74 | colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.background), 75 | shape = RoundedCornerShape(8.dp), 76 | border = BorderStroke( 77 | 2.dp, 78 | LightGray 79 | ) 80 | ) { 81 | Text( 82 | text = stringResource(R.string.cancel), 83 | color = LightGray 84 | ) 85 | } 86 | Button( 87 | onClick = { onDelete() }, 88 | colors = ButtonDefaults.buttonColors(containerColor = Red), 89 | shape = RoundedCornerShape(8.dp), 90 | ) { 91 | Text( 92 | text = stringResource(R.string.delete), 93 | color = Color.Black 94 | ) 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | @Preview() 103 | @Composable 104 | fun ConfirmDeleteDialogPreview() { 105 | SnaptickTheme { 106 | ConfirmDeleteDialog( 107 | {}, 108 | {}) 109 | } 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/add_edit_screen/components/CustomDurationDialogComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.add_edit_screen.components 2 | 3 | import androidx.compose.foundation.border 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material3.Card 11 | import androidx.compose.material3.CardDefaults 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.compose.ui.tooling.preview.Preview 19 | import androidx.compose.ui.unit.dp 20 | import androidx.compose.ui.window.Dialog 21 | import com.commandiron.wheel_picker_compose.WheelTimePicker 22 | import com.vishal2376.snaptick.R 23 | import com.vishal2376.snaptick.presentation.common.durationTextStyle 24 | import com.vishal2376.snaptick.presentation.common.h3TextStyle 25 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 26 | import java.time.LocalTime 27 | 28 | @Composable 29 | fun CustomDurationDialogComponent( 30 | duration: Int = 120, 31 | onClose: () -> Unit, 32 | onSelect: (LocalTime) -> Unit 33 | ) { 34 | Dialog(onDismissRequest = { onClose() }) { 35 | 36 | 37 | val hours = duration / 60 38 | val minutes = duration % 60 39 | 40 | var customDuration = LocalTime.of(hours, minutes) 41 | 42 | Card( 43 | modifier = Modifier 44 | .fillMaxWidth() 45 | .border( 46 | 4.dp, 47 | MaterialTheme.colorScheme.primaryContainer, 48 | RoundedCornerShape(16.dp) 49 | ), 50 | shape = RoundedCornerShape(16.dp), 51 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.background) 52 | ) { 53 | Column( 54 | modifier = Modifier 55 | .fillMaxWidth() 56 | .padding( 57 | 32.dp, 58 | 16.dp 59 | ), 60 | horizontalAlignment = Alignment.CenterHorizontally, 61 | verticalArrangement = Arrangement.spacedBy(24.dp) 62 | ) { 63 | Text( 64 | text = stringResource(R.string.custom_duration), 65 | color = MaterialTheme.colorScheme.onBackground, 66 | style = durationTextStyle 67 | ) 68 | WheelTimePicker( 69 | startTime = LocalTime.of(hours, minutes), 70 | textColor = MaterialTheme.colorScheme.onBackground, 71 | onSnappedTime = { customDuration = it } 72 | ) 73 | Text( 74 | modifier = Modifier 75 | .padding(8.dp) 76 | .clickable { 77 | onSelect(customDuration) 78 | onClose() 79 | } 80 | .align(Alignment.End), 81 | text = stringResource(R.string.done), 82 | style = h3TextStyle, 83 | color = MaterialTheme.colorScheme.primary 84 | ) 85 | } 86 | } 87 | } 88 | } 89 | 90 | @Preview 91 | @Composable 92 | fun CustomDurationDialogComponentPreview() { 93 | SnaptickTheme { 94 | CustomDurationDialogComponent(onClose = {}, onSelect = {}) 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/add_edit_screen/components/ShowNativeTimePickerComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.add_edit_screen.components 2 | 3 | import androidx.compose.foundation.border 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.size 10 | import androidx.compose.foundation.shape.RoundedCornerShape 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.filled.AccessTime 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.unit.dp 21 | import com.vishal2376.snaptick.presentation.common.taskTextStyle 22 | import java.time.LocalTime 23 | import java.time.format.DateTimeFormatter 24 | 25 | @Composable 26 | fun ShowNativeTimePicker(time: LocalTime, is24hourFormat: Boolean = false, onClick: () -> Unit) { 27 | Box(modifier = Modifier.padding(vertical = 16.dp)) { 28 | Row( 29 | modifier = Modifier 30 | .clip(RoundedCornerShape(16.dp)) 31 | .clickable { onClick() } 32 | .border(2.dp, MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(16.dp)) 33 | .padding(10.dp), 34 | verticalAlignment = Alignment.CenterVertically, 35 | horizontalArrangement = Arrangement.spacedBy(8.dp) 36 | ) { 37 | Icon( 38 | imageVector = Icons.Default.AccessTime, 39 | contentDescription = null, 40 | tint = MaterialTheme.colorScheme.onBackground, 41 | modifier = Modifier.size(24.dp) 42 | ) 43 | val dtf = if (is24hourFormat) 44 | DateTimeFormatter.ofPattern("HH : mm") 45 | else 46 | DateTimeFormatter.ofPattern("hh : mm a") 47 | 48 | Text( 49 | text = time.format(dtf), 50 | style = taskTextStyle, 51 | color = MaterialTheme.colorScheme.onBackground 52 | ) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/add_edit_screen/components/WeekDaysComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.add_edit_screen.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxWidth 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.Checkbox 13 | import androidx.compose.material3.MaterialTheme 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.getValue 17 | import androidx.compose.runtime.mutableStateOf 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.runtime.setValue 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.draw.alpha 23 | import androidx.compose.ui.tooling.preview.Preview 24 | import androidx.compose.ui.unit.dp 25 | import com.vishal2376.snaptick.presentation.common.infoDescTextStyle 26 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 27 | 28 | @Composable 29 | fun WeekDaysComponent( 30 | defaultRepeatedDays: List = listOf(0), 31 | onChange: (String) -> Unit 32 | ) { 33 | val weekDays = listOf("M", "T", "W", "T", "F", "S", "S") 34 | var selectedDays by remember { mutableStateOf(defaultRepeatedDays) } 35 | 36 | Row( 37 | modifier = Modifier 38 | .fillMaxWidth() 39 | .padding(24.dp, 8.dp), 40 | horizontalArrangement = Arrangement.SpaceAround 41 | ) { 42 | weekDays.forEachIndexed { index, day -> 43 | WeekDaysItemComponent( 44 | title = day, 45 | isSelected = selectedDays.contains(index) 46 | ) { isChecked -> 47 | selectedDays = if (isChecked) { 48 | selectedDays + index 49 | } else { 50 | if (selectedDays.size > 1) { 51 | selectedDays - index 52 | } else { 53 | selectedDays 54 | } 55 | } 56 | val sortedDaysList = selectedDays.sorted() 57 | onChange(sortedDaysList.joinToString(separator = ",")) 58 | } 59 | } 60 | } 61 | } 62 | 63 | @Composable 64 | fun WeekDaysItemComponent(title: String, isSelected: Boolean, onChange: (Boolean) -> Unit) { 65 | 66 | var bgColor = MaterialTheme.colorScheme.background 67 | var textColor = MaterialTheme.colorScheme.onBackground 68 | var borderWidth = 2.dp 69 | 70 | if (isSelected) { 71 | bgColor = MaterialTheme.colorScheme.primary 72 | textColor = MaterialTheme.colorScheme.onPrimary 73 | borderWidth = 0.dp 74 | } 75 | 76 | Box( 77 | modifier = Modifier 78 | .size(32.dp) 79 | .background(bgColor, CircleShape) 80 | .border(borderWidth, MaterialTheme.colorScheme.primaryContainer, CircleShape), 81 | contentAlignment = Alignment.Center 82 | ) { 83 | Checkbox(modifier = Modifier.alpha(0f), 84 | checked = isSelected, 85 | onCheckedChange = { onChange(it) } 86 | ) 87 | Text(text = title, color = textColor, style = infoDescTextStyle) 88 | } 89 | } 90 | 91 | @Preview() 92 | @Composable 93 | fun WeekDaysComponentPreview() { 94 | SnaptickTheme { 95 | WeekDaysComponent(listOf(2, 3), {}) 96 | } 97 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/calender_screen/component/WeekDayComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.calender_screen.component 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.width 11 | import androidx.compose.foundation.shape.RoundedCornerShape 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.draw.clip 18 | import androidx.compose.ui.platform.LocalConfiguration 19 | import androidx.compose.ui.tooling.preview.Preview 20 | import androidx.compose.ui.unit.dp 21 | import com.kizitonwose.calendar.core.WeekDay 22 | import com.kizitonwose.calendar.core.WeekDayPosition 23 | import com.vishal2376.snaptick.presentation.common.infoTextStyle 24 | import com.vishal2376.snaptick.presentation.common.taskDescTextStyle 25 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 26 | import java.time.LocalDate 27 | import java.time.format.DateTimeFormatter 28 | import java.time.format.TextStyle 29 | import java.util.Locale 30 | 31 | private val dateFormatter = DateTimeFormatter.ofPattern("dd") 32 | 33 | @Composable 34 | fun WeekDayComponent( 35 | day: WeekDay, 36 | selected: Boolean = false, 37 | indicator: Boolean = true, 38 | onClick: (LocalDate) -> Unit = {}, 39 | ) { 40 | val textColor = if (selected) { 41 | MaterialTheme.colorScheme.onPrimary 42 | } else if (indicator) { 43 | MaterialTheme.colorScheme.primary 44 | } else { 45 | MaterialTheme.colorScheme.onBackground 46 | } 47 | 48 | val configuration = LocalConfiguration.current 49 | val screenWidth = configuration.screenWidthDp.dp 50 | Box( 51 | modifier = Modifier 52 | .width(screenWidth / 7) 53 | .padding(4.dp) 54 | .clip(RoundedCornerShape(16.dp)) 55 | .background(if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.primaryContainer) 56 | .border( 57 | if (day.date == LocalDate.now()) 2.dp else (-1).dp, 58 | MaterialTheme.colorScheme.primary, 59 | RoundedCornerShape(16.dp) 60 | ) 61 | .clickable { onClick(day.date) }, 62 | contentAlignment = Alignment.Center, 63 | ) { 64 | Column( 65 | modifier = Modifier.padding(bottom = 10.dp, top = 6.dp), 66 | horizontalAlignment = Alignment.CenterHorizontally, 67 | verticalArrangement = Arrangement.Center 68 | ) { 69 | Text( 70 | text = dateFormatter.format(day.date), 71 | style = infoTextStyle, 72 | color = textColor 73 | ) 74 | Text( 75 | text = day.date.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault()), 76 | style = taskDescTextStyle, 77 | color = textColor, 78 | ) 79 | } 80 | } 81 | } 82 | 83 | @Preview 84 | @Composable 85 | fun WeekDayComponentPreview() { 86 | SnaptickTheme { 87 | WeekDayComponent(WeekDay(LocalDate.now(), position = WeekDayPosition.InDate)) 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/AppTheme.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | enum class AppTheme { 4 | Light, Dark, Amoled; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/CalenderView.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | enum class CalenderView { 4 | WEEKLY, 5 | MONTHLY 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/FilterTasksUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | import java.time.LocalDate 5 | import java.time.YearMonth 6 | 7 | fun getTasksByMonth(tasks: List, month: YearMonth = YearMonth.now()): List { 8 | return tasks.filter { task -> 9 | YearMonth.from(task.date) == month 10 | } 11 | } 12 | 13 | fun filterTasksByDate(tasks: List, date: LocalDate = LocalDate.now()): List { 14 | return tasks.filter { task -> 15 | task.date == date 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/NavDrawerItem.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.BugReport 5 | import androidx.compose.material.icons.filled.Chat 6 | import androidx.compose.material.icons.filled.Share 7 | import androidx.compose.material.icons.filled.Star 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import com.vishal2376.snaptick.R 10 | 11 | enum class NavDrawerItem(val stringId: Int, val icon: ImageVector) { 12 | REPORT_BUGS(R.string.report_bugs, Icons.Default.BugReport), 13 | SUGGESTIONS(R.string.suggestions, Icons.Default.Chat), 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/NotificationPermissionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | import android.Manifest 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import androidx.activity.compose.rememberLauncherForActivityResult 7 | import androidx.activity.result.contract.ActivityResultContracts 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.LaunchedEffect 10 | import androidx.compose.ui.platform.LocalContext 11 | 12 | @Composable 13 | fun NotificationPermissionHandler( 14 | onPermissionGranted: () -> Unit, 15 | onPermissionDenied: () -> Unit 16 | ) { 17 | val context = LocalContext.current 18 | 19 | val launcher = rememberLauncherForActivityResult( 20 | contract = ActivityResultContracts.RequestPermission() 21 | ) { isGranted -> 22 | if (isGranted) { 23 | onPermissionGranted() 24 | } else { 25 | onPermissionDenied() 26 | } 27 | } 28 | 29 | // Check the current permission state 30 | LaunchedEffect(Unit) { 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 32 | val isGranted = 33 | context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED 34 | 35 | if (!isGranted) { 36 | launcher.launch(Manifest.permission.POST_NOTIFICATIONS) 37 | } else { 38 | onPermissionGranted() 39 | } 40 | } else { 41 | onPermissionGranted() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/Priority.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | enum class Priority(val displayText: String) { 4 | LOW("Low"), 5 | MEDIUM("Medium"), 6 | HIGH("High") 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/ShowTimePicker.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.animation.expandVertically 6 | import androidx.compose.animation.fadeIn 7 | import androidx.compose.animation.fadeOut 8 | import androidx.compose.animation.shrinkVertically 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.runtime.Composable 11 | import com.commandiron.wheel_picker_compose.WheelTimePicker 12 | import com.commandiron.wheel_picker_compose.core.TimeFormat 13 | import java.time.LocalTime 14 | 15 | @Composable 16 | fun ShowTimePicker( 17 | time: LocalTime, 18 | isTimeUpdated: Boolean = false, 19 | is24hourFormat: Boolean = false, 20 | onSelect: (LocalTime) -> Unit 21 | ) { 22 | AnimatedVisibility( 23 | isTimeUpdated, 24 | enter = fadeIn() + expandVertically(), 25 | exit = fadeOut() + shrinkVertically(tween(0)) 26 | ) { 27 | WheelTimePicker( 28 | timeFormat = if (is24hourFormat) TimeFormat.HOUR_24 else TimeFormat.AM_PM, 29 | startTime = time, 30 | textColor = MaterialTheme.colorScheme.onBackground, 31 | onSnappedTime = onSelect 32 | ) 33 | } 34 | AnimatedVisibility( 35 | !isTimeUpdated, 36 | enter = fadeIn() + expandVertically(), 37 | exit = fadeOut() + shrinkVertically(tween(0)) 38 | ) { 39 | WheelTimePicker( 40 | timeFormat = if (is24hourFormat) TimeFormat.HOUR_24 else TimeFormat.AM_PM, 41 | startTime = time, 42 | textColor = MaterialTheme.colorScheme.onBackground, 43 | onSnappedTime = onSelect 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/SortTask.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | import com.vishal2376.snaptick.R 4 | 5 | enum class SortTask(val stringId: Int) { 6 | BY_PRIORITY_ASCENDING(R.string.priority_low_to_high), 7 | BY_PRIORITY_DESCENDING(R.string.priority_high_to_low), 8 | BY_START_TIME_ASCENDING(R.string.start_time_latest_at_bottom), 9 | BY_START_TIME_DESCENDING(R.string.start_time_latest_at_top), 10 | BY_CREATE_TIME_ASCENDING(R.string.creation_time_latest_at_bottom), 11 | BY_CREATE_TIME_DESCENDING(R.string.creation_time_latest_at_top), 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/common/TextStyles.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.common 2 | 3 | import androidx.compose.ui.text.TextStyle 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 androidx.compose.ui.unit.sp 8 | import com.vishal2376.snaptick.R 9 | 10 | 11 | val fontRoboto = FontFamily(Font(R.font.roboto)) 12 | val fontRobotoMono = FontFamily(Font(R.font.roboto_mono)) 13 | val fontRobotoMonoThin = FontFamily(Font(R.font.roboto_mono_thin)) 14 | val fontMontserrat = FontFamily(Font(R.font.montserrat)) 15 | 16 | val durationTextStyle = TextStyle( 17 | fontSize = 20.sp, 18 | fontFamily = fontRobotoMono 19 | ) 20 | 21 | var h1TextStyle = TextStyle( 22 | fontSize = 24.sp, 23 | fontFamily = fontMontserrat, 24 | fontWeight = FontWeight.Bold 25 | ) 26 | 27 | var h2TextStyle = TextStyle( 28 | fontSize = 20.sp, 29 | fontFamily = fontMontserrat, 30 | fontWeight = FontWeight.Bold 31 | ) 32 | var h3TextStyle = TextStyle( 33 | fontSize = 16.sp, 34 | fontFamily = fontMontserrat, 35 | fontWeight = FontWeight.Bold 36 | ) 37 | 38 | val infoTextStyle = TextStyle( 39 | fontSize = 18.sp, 40 | fontFamily = fontMontserrat, 41 | fontWeight = FontWeight.SemiBold 42 | ) 43 | 44 | val infoDescTextStyle = TextStyle( 45 | fontSize = 14.sp, 46 | fontFamily = fontRoboto 47 | ) 48 | 49 | var taskDescTextStyle = TextStyle( 50 | fontSize = 12.sp, 51 | fontFamily = fontRoboto 52 | ) 53 | 54 | var taskTextStyle = TextStyle( 55 | fontSize = 16.sp, 56 | fontFamily = fontRoboto, 57 | ) 58 | 59 | var timerTextStyle = TextStyle( 60 | fontSize = 42.sp, 61 | fontFamily = fontRobotoMonoThin 62 | ) 63 | 64 | var settingItemTextStyle = TextStyle( 65 | fontSize = 18.sp, 66 | fontFamily = fontMontserrat, 67 | ) 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/free_time_screen/components/CustomPieChart.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.free_time_screen.components 2 | 3 | import androidx.compose.animation.core.Animatable 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.Canvas 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.LaunchedEffect 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.draw.rotate 15 | import androidx.compose.ui.graphics.drawscope.Stroke 16 | import androidx.compose.ui.tooling.preview.Preview 17 | import androidx.compose.ui.unit.Dp 18 | import androidx.compose.ui.unit.dp 19 | import com.vishal2376.snaptick.ui.theme.pieChartColors 20 | 21 | @Composable 22 | fun CustomPieChart( 23 | data: List, 24 | arcWidth: Dp = 30.dp, 25 | startAngle: Float = -180f, 26 | pieChartSize: Dp = 200.dp, 27 | animDuration: Int = 1000 28 | ) { 29 | // calculate each arc value 30 | val totalSum = data.sum() 31 | val arcValues = mutableListOf() 32 | data.forEachIndexed { index, value -> 33 | val arc = value.toFloat() / totalSum.toFloat() * 360f 34 | arcValues.add(index, arc) 35 | } 36 | 37 | var newStartAngle = startAngle 38 | 39 | // animations 40 | val animationProgress = remember { Animatable(0f) } 41 | LaunchedEffect(Unit) { 42 | animationProgress.animateTo(1f, animationSpec = tween(animDuration)) 43 | } 44 | 45 | // draw pie chart 46 | val totalColors = pieChartColors.size 47 | 48 | Column( 49 | modifier = Modifier.size(pieChartSize * 1.5f), 50 | horizontalAlignment = Alignment.CenterHorizontally, 51 | verticalArrangement = Arrangement.Center 52 | ) { 53 | Canvas( 54 | modifier = Modifier 55 | .size(pieChartSize) 56 | .rotate(90f * animationProgress.value) 57 | ) { 58 | if (totalSum == 0L) { 59 | drawCircle( 60 | color = pieChartColors[totalColors - 1], 61 | style = Stroke(width = arcWidth.toPx()) 62 | ) 63 | } else { 64 | arcValues.forEachIndexed { index, arcValue -> 65 | drawArc( 66 | color = pieChartColors[index % totalColors], 67 | startAngle = newStartAngle, 68 | useCenter = false, 69 | sweepAngle = arcValue * animationProgress.value, 70 | style = Stroke(width = arcWidth.toPx()) 71 | ) 72 | newStartAngle += arcValue 73 | } 74 | } 75 | } 76 | } 77 | 78 | } 79 | 80 | @Preview 81 | @Composable 82 | fun CustomPieChartPreview() { 83 | val time = listOf(10, 20, 40, 15, 60, 20, 10) 84 | CustomPieChart(time) 85 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/free_time_screen/components/PieChartItemComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.free_time_screen.components 2 | 3 | import androidx.compose.animation.core.Animatable 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.ExperimentalFoundationApi 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.basicMarquee 8 | import androidx.compose.foundation.layout.Arrangement 9 | import androidx.compose.foundation.layout.Box 10 | import androidx.compose.foundation.layout.Row 11 | import androidx.compose.foundation.layout.Spacer 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.foundation.layout.width 16 | import androidx.compose.foundation.shape.CircleShape 17 | import androidx.compose.foundation.shape.RoundedCornerShape 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.material3.Text 20 | import androidx.compose.runtime.Composable 21 | import androidx.compose.runtime.LaunchedEffect 22 | import androidx.compose.runtime.remember 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.graphics.Color 26 | import androidx.compose.ui.graphics.graphicsLayer 27 | import androidx.compose.ui.tooling.preview.Preview 28 | import androidx.compose.ui.unit.dp 29 | import com.vishal2376.snaptick.domain.model.Task 30 | import com.vishal2376.snaptick.presentation.common.taskTextStyle 31 | import com.vishal2376.snaptick.ui.theme.Blue 32 | import com.vishal2376.snaptick.util.DummyTasks 33 | import kotlinx.coroutines.delay 34 | 35 | @OptIn(ExperimentalFoundationApi::class) 36 | @Composable 37 | fun PieChartItemComponent(task: Task, itemColor: Color, animDelay: Int = 100) { 38 | 39 | val alphaAnimation = remember { Animatable(initialValue = 0f) } 40 | 41 | LaunchedEffect(animDelay) { 42 | delay(animDelay.toLong()) 43 | alphaAnimation.animateTo(targetValue = 1f, animationSpec = tween(1000)) 44 | } 45 | 46 | Box( 47 | modifier = Modifier 48 | .graphicsLayer { 49 | alpha = alphaAnimation.value 50 | } 51 | .fillMaxWidth() 52 | .background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(8.dp)) 53 | .padding(16.dp, 20.dp) 54 | ) { 55 | 56 | Row( 57 | modifier = Modifier.fillMaxWidth(), 58 | verticalAlignment = Alignment.CenterVertically, 59 | horizontalArrangement = Arrangement.spacedBy(8.dp) 60 | ) { 61 | 62 | Box( 63 | modifier = Modifier 64 | .size(16.dp) 65 | .background(itemColor, CircleShape), 66 | ) 67 | 68 | Spacer(modifier = Modifier.width(0.dp)) 69 | 70 | Text( 71 | text = task.title, 72 | modifier = Modifier 73 | .basicMarquee(delayMillis = 1000) 74 | .weight(1f), 75 | style = taskTextStyle, 76 | color = MaterialTheme.colorScheme.onBackground, 77 | ) 78 | 79 | if (!task.isAllDayTaskEnabled()) { 80 | Text( 81 | text = task.getFormattedDuration(), 82 | style = taskTextStyle, 83 | color = MaterialTheme.colorScheme.onBackground 84 | ) 85 | } 86 | } 87 | } 88 | } 89 | 90 | @Preview 91 | @Composable 92 | fun PieChartItemComponentPreview() { 93 | val task = DummyTasks.dummyTasks[0] 94 | PieChartItemComponent(task, Blue) 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/home_screen/HomeScreenEvent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.home_screen 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | 5 | sealed class HomeScreenEvent { 6 | data class OnCompleted(val taskId: Int, val isCompleted: Boolean) : HomeScreenEvent() 7 | data class OnSwipeTask(val task: Task) : HomeScreenEvent() 8 | data class OnEditTask(val taskId: Int) : HomeScreenEvent() 9 | data class OnPomodoro(val taskId: Int) : HomeScreenEvent() 10 | data class OnDeleteTask(val taskId: Int) : HomeScreenEvent() 11 | data object OnUndoDelete : HomeScreenEvent() 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/home_screen/components/EmptyTaskComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.home_screen.components 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.material3.MaterialTheme 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.res.painterResource 15 | import androidx.compose.ui.res.stringResource 16 | import androidx.compose.ui.tooling.preview.Preview 17 | import androidx.compose.ui.unit.dp 18 | import com.vishal2376.snaptick.R 19 | import com.vishal2376.snaptick.presentation.common.h1TextStyle 20 | 21 | @Composable 22 | fun EmptyTaskComponent(modifier: Modifier = Modifier) { 23 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) 24 | { 25 | Column( 26 | modifier = modifier.size(250.dp), 27 | horizontalAlignment = Alignment.CenterHorizontally, 28 | verticalArrangement = Arrangement.spacedBy(24.dp) 29 | ) { 30 | Image( 31 | painter = painterResource(id = R.drawable.no_tasks), 32 | contentDescription = null 33 | ) 34 | Text( 35 | text = stringResource(R.string.no_tasks), 36 | style = h1TextStyle, 37 | color = MaterialTheme.colorScheme.onBackground 38 | ) 39 | } 40 | } 41 | } 42 | 43 | @Preview 44 | @Composable 45 | private fun EmptyTaskComponentPreview() { 46 | EmptyTaskComponent() 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/home_screen/components/InfoComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.home_screen.components 2 | 3 | import androidx.compose.foundation.border 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.fillMaxWidth 9 | import androidx.compose.foundation.layout.height 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.layout.size 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material3.Card 14 | import androidx.compose.material3.CardDefaults 15 | import androidx.compose.material3.ExperimentalMaterial3Api 16 | import androidx.compose.material3.Icon 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.material3.Text 19 | import androidx.compose.runtime.Composable 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.res.painterResource 24 | import androidx.compose.ui.tooling.preview.Preview 25 | import androidx.compose.ui.unit.dp 26 | import com.vishal2376.snaptick.R 27 | import com.vishal2376.snaptick.presentation.common.infoDescTextStyle 28 | import com.vishal2376.snaptick.presentation.common.infoTextStyle 29 | import com.vishal2376.snaptick.ui.theme.Black500 30 | import com.vishal2376.snaptick.ui.theme.Blue 31 | 32 | @OptIn(ExperimentalMaterial3Api::class) 33 | @Composable 34 | fun InfoComponent( 35 | title: String, 36 | desc: String, 37 | icon: Int, 38 | backgroundColor: Color, 39 | dynamicTheme: Boolean, 40 | modifier: Modifier, 41 | onClick: () -> Unit 42 | ) { 43 | var borderColor = Color.Transparent 44 | var bgColor = backgroundColor 45 | var contentColor = Black500 46 | 47 | if (dynamicTheme) { 48 | borderColor = MaterialTheme.colorScheme.onPrimaryContainer 49 | bgColor = MaterialTheme.colorScheme.primaryContainer 50 | contentColor = MaterialTheme.colorScheme.onPrimaryContainer 51 | } 52 | 53 | Card( 54 | modifier = modifier.border( 55 | 2.dp, 56 | borderColor, 57 | RoundedCornerShape(16.dp) 58 | ), 59 | shape = RoundedCornerShape(16.dp), 60 | colors = CardDefaults.cardColors(containerColor = bgColor), 61 | onClick = { onClick() } 62 | ) { 63 | Column( 64 | modifier = Modifier 65 | .fillMaxWidth() 66 | .padding(8.dp, 16.dp), 67 | horizontalAlignment = Alignment.CenterHorizontally, 68 | verticalArrangement = Arrangement.Center 69 | ) { 70 | 71 | Text( 72 | text = title, 73 | style = infoTextStyle, 74 | color = contentColor 75 | ) 76 | 77 | Spacer(modifier = Modifier.height(4.dp)) 78 | Row( 79 | verticalAlignment = Alignment.CenterVertically, 80 | horizontalArrangement = Arrangement.spacedBy(8.dp) 81 | ) { 82 | Icon( 83 | painter = painterResource(id = icon), 84 | contentDescription = null, 85 | tint = contentColor, 86 | modifier = Modifier.size(20.dp) 87 | ) 88 | Text( 89 | text = desc, 90 | style = infoDescTextStyle, 91 | color = contentColor 92 | ) 93 | } 94 | } 95 | } 96 | } 97 | 98 | @Preview 99 | @Composable 100 | private fun InfoComponentPreview() { 101 | InfoComponent( 102 | title = "Completed", 103 | desc = "1/3 Tasks", 104 | icon = R.drawable.ic_task_list, 105 | dynamicTheme = false, 106 | backgroundColor = Blue, 107 | modifier = Modifier, 108 | onClick = {} 109 | ) 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/home_screen/components/SortTaskDialogComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.home_screen.components 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.Card 8 | import androidx.compose.material3.CardDefaults 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.RadioButton 11 | import androidx.compose.material3.RadioButtonDefaults 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.runtime.mutableStateOf 16 | import androidx.compose.runtime.remember 17 | import androidx.compose.runtime.setValue 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.res.stringResource 21 | import androidx.compose.ui.tooling.preview.Preview 22 | import androidx.compose.ui.unit.dp 23 | import androidx.compose.ui.window.Dialog 24 | import com.vishal2376.snaptick.R 25 | import com.vishal2376.snaptick.presentation.common.SortTask 26 | import com.vishal2376.snaptick.presentation.common.h2TextStyle 27 | import com.vishal2376.snaptick.presentation.common.h3TextStyle 28 | import com.vishal2376.snaptick.presentation.common.taskTextStyle 29 | import com.vishal2376.snaptick.ui.theme.Blue 30 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 31 | 32 | @Composable 33 | fun SortTaskDialogComponent( 34 | defaultSortTask: SortTask, 35 | onClose: () -> Unit, 36 | onSelect: (SortTask) -> Unit 37 | ) { 38 | 39 | var selectedOption by remember { 40 | mutableStateOf(defaultSortTask) 41 | } 42 | 43 | Dialog(onDismissRequest = { onClose() }) { 44 | Card( 45 | modifier = Modifier 46 | .fillMaxWidth(1f), 47 | colors = CardDefaults.cardColors( 48 | containerColor = MaterialTheme.colorScheme.primaryContainer, 49 | contentColor = MaterialTheme.colorScheme.onPrimaryContainer 50 | ) 51 | ) { 52 | Text( 53 | modifier = Modifier.padding( 54 | start = 16.dp, 55 | top = 24.dp, 56 | bottom = 16.dp 57 | ), 58 | text = stringResource(R.string.sort_tasks_by), 59 | style = h2TextStyle 60 | ) 61 | 62 | SortTask.entries.forEach { 63 | CustomRadioButton( 64 | label = stringResource(id = it.stringId), 65 | isSelected = selectedOption == it 66 | ) { 67 | selectedOption = it 68 | } 69 | } 70 | 71 | Text( 72 | modifier = Modifier 73 | .padding( 74 | bottom = 24.dp, 75 | end = 32.dp 76 | ) 77 | .clickable { onSelect(selectedOption) } 78 | .align(Alignment.End), 79 | text = stringResource(R.string.select), 80 | style = h3TextStyle, 81 | color = MaterialTheme.colorScheme.primary 82 | ) 83 | } 84 | } 85 | } 86 | 87 | @Composable 88 | fun CustomRadioButton(label: String, isSelected: Boolean, onClick: () -> Unit) { 89 | Row( 90 | verticalAlignment = Alignment.CenterVertically, 91 | modifier = Modifier 92 | .fillMaxWidth() 93 | .padding(bottom = 8.dp) 94 | .clickable { onClick() }) { 95 | RadioButton( 96 | colors = RadioButtonDefaults.colors(selectedColor = MaterialTheme.colorScheme.primary), 97 | selected = isSelected, 98 | onClick = { onClick() }) 99 | Text( 100 | text = label, 101 | style = taskTextStyle, 102 | color = MaterialTheme.colorScheme.onPrimaryContainer 103 | ) 104 | } 105 | } 106 | 107 | @Preview 108 | @Composable 109 | fun SortTaskDialogComponentPreview() { 110 | SnaptickTheme { 111 | SortTaskDialogComponent( 112 | SortTask.BY_CREATE_TIME_ASCENDING, 113 | {}, 114 | {}) 115 | } 116 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/main/MainEvent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.main 2 | 3 | import android.content.Context 4 | import com.vishal2376.snaptick.presentation.common.AppTheme 5 | import com.vishal2376.snaptick.presentation.common.CalenderView 6 | import com.vishal2376.snaptick.presentation.common.NavDrawerItem 7 | import com.vishal2376.snaptick.presentation.common.SortTask 8 | import com.vishal2376.snaptick.presentation.common.SwipeBehavior 9 | import java.time.LocalDate 10 | import java.time.LocalTime 11 | 12 | sealed class MainEvent { 13 | data class UpdateAppTheme(val theme: AppTheme, val context: Context) : MainEvent() 14 | data class UpdateDynamicTheme(val isEnabled: Boolean, val context: Context) : MainEvent() 15 | data class UpdateTimePicker(val isWheelTimePicker: Boolean, val context: Context) : MainEvent() 16 | data class UpdateFirstTimeOpened(val isFirstTimeOpened: Boolean) : MainEvent() 17 | data class UpdateTimeFormat(val isTimeFormat: Boolean, val context: Context) : MainEvent() 18 | data class UpdateSleepTime(val sleepTime: LocalTime, val context: Context) : MainEvent() 19 | data class UpdateLanguage(val language: String, val context: Context) : MainEvent() 20 | data class UpdateSortByTask(val sortTask: SortTask, val context: Context) : MainEvent() 21 | data class UpdateCalenderDate(val date: LocalDate?) : MainEvent() 22 | data class UpdateShowWhatsNew(val showWhatsNew: Boolean, val context: Context) : MainEvent() 23 | data class OnClickNavDrawerItem(val context: Context, val item: NavDrawerItem) : MainEvent() 24 | data class UpdateBuildVersionCode(val context: Context, val versionCode: Int) : MainEvent() 25 | data class UpdateCalenderView(val calenderView: CalenderView, val context: Context) : 26 | MainEvent() 27 | 28 | data class UpdateSwipeBehaviour(val swipeBehaviour: SwipeBehavior, val context: Context) : 29 | MainEvent() 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/main/MainState.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.main 2 | 3 | import com.vishal2376.snaptick.presentation.common.AppTheme 4 | import com.vishal2376.snaptick.presentation.common.CalenderView 5 | import com.vishal2376.snaptick.presentation.common.SortTask 6 | import com.vishal2376.snaptick.presentation.common.SwipeBehavior 7 | import java.time.LocalDate 8 | import java.time.LocalTime 9 | import java.util.Locale 10 | 11 | data class MainState( 12 | val buildVersion: String = "0.0", 13 | val buildVersionCode: Int = 1, 14 | val firstTimeOpened: Boolean = true, 15 | val showWhatsNew: Boolean = false, 16 | val theme: AppTheme = AppTheme.Dark, 17 | val dynamicTheme: Boolean = false, 18 | val sortBy: SortTask = SortTask.BY_START_TIME_ASCENDING, 19 | var totalTaskDuration: Long = 0, 20 | val durationList: List = listOf(30, 60, 90, 0), 21 | val streak: Int = -1, 22 | val sleepTime: LocalTime = LocalTime.of(23, 59), 23 | val language: String = Locale.ENGLISH.language, 24 | val isWheelTimePicker: Boolean = true, 25 | val is24hourTimeFormat: Boolean = false, 26 | val calenderView: CalenderView = CalenderView.MONTHLY, 27 | val calenderDate: LocalDate? = null, 28 | val swipeBehaviour: SwipeBehavior = SwipeBehavior.DELETE 29 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/navigation/Routes.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.navigation 2 | 3 | enum class Routes { 4 | HomeScreen, 5 | AddTaskScreen, 6 | EditTaskScreen, 7 | CompletedTaskScreen, 8 | PomodoroScreen, 9 | FreeTimeScreen, 10 | ThisWeekTaskScreen, 11 | CalenderScreen, 12 | SettingsScreen, 13 | AboutScreen, 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/pomodoro_screen/PomodoroScreenEvent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.pomodoro_screen 2 | 3 | sealed class PomodoroScreenEvent { 4 | data class OnCompleted(val taskId: Int, val isCompleted: Boolean) : PomodoroScreenEvent() 5 | data class OnDestroyScreen(val taskId: Int, val remainingTime: Long) : PomodoroScreenEvent() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/pomodoro_screen/components/CustomCircularProgressBar.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.pomodoro_screen.components 2 | 3 | import androidx.compose.foundation.Canvas 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.geometry.Offset 9 | import androidx.compose.ui.geometry.Size 10 | import androidx.compose.ui.geometry.center 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.graphics.StrokeCap 13 | import androidx.compose.ui.graphics.drawscope.Stroke 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.Dp 16 | import androidx.compose.ui.unit.dp 17 | import com.vishal2376.snaptick.ui.theme.SnaptickTheme 18 | 19 | @Composable 20 | fun CustomCircularProgressBar( 21 | progress: Float = 10f, 22 | progressMax: Float = 100f, 23 | progressBarColor: Color = MaterialTheme.colorScheme.primary, 24 | progressBarWidth: Dp = 10.dp, 25 | backgroundProgressBarColor: Color = MaterialTheme.colorScheme.secondary, 26 | backgroundProgressBarWidth: Dp = 3.dp, 27 | roundBorder: Boolean = true, 28 | startAngle: Float = 0f 29 | ) { 30 | Canvas(modifier = Modifier.size(250.dp)) { 31 | 32 | val canvasSize = size.minDimension 33 | 34 | val radius = canvasSize / 2 - maxOf( 35 | backgroundProgressBarWidth, 36 | progressBarWidth 37 | ).toPx() / 2 38 | 39 | drawCircle( 40 | color = backgroundProgressBarColor, 41 | radius = radius, 42 | center = size.center, 43 | style = Stroke(width = backgroundProgressBarWidth.toPx()) 44 | ) 45 | 46 | drawArc( 47 | color = progressBarColor, 48 | startAngle = 270f + startAngle, 49 | sweepAngle = (progress / progressMax) * 360f, 50 | useCenter = false, 51 | topLeft = size.center - Offset( 52 | radius, 53 | radius 54 | ), 55 | size = Size( 56 | radius * 2, 57 | radius * 2 58 | ), 59 | style = Stroke( 60 | width = progressBarWidth.toPx(), 61 | cap = if (roundBorder) StrokeCap.Round else StrokeCap.Butt 62 | ) 63 | ) 64 | } 65 | } 66 | 67 | @Preview() 68 | @Composable 69 | fun CustomCircularProgressBarPreview() { 70 | SnaptickTheme { 71 | CustomCircularProgressBar() 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/settings/common/SettingCategoryItem.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.settings.common 2 | 3 | data class SettingCategoryItem( 4 | val title: String, 5 | val resId: Int, 6 | val onClick: () -> Unit = {} 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/settings/common/ToggleOption.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.settings.common 2 | 3 | import androidx.compose.animation.core.Animatable 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.Arrangement 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.width 13 | import androidx.compose.foundation.shape.RoundedCornerShape 14 | import androidx.compose.material3.MaterialTheme 15 | import androidx.compose.material3.Text 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.runtime.LaunchedEffect 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.draw.clip 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.unit.dp 24 | import com.vishal2376.snaptick.presentation.common.h3TextStyle 25 | 26 | @Composable 27 | fun ToggleOptions( 28 | title: String, isSelected: Boolean, 29 | selectedBgColor: Color = MaterialTheme.colorScheme.primary, 30 | onClick: () -> Unit 31 | ) { 32 | Column( 33 | verticalArrangement = Arrangement.spacedBy(4.dp), 34 | horizontalAlignment = Alignment.CenterHorizontally 35 | ) { 36 | Box( 37 | modifier = Modifier 38 | .clip(RoundedCornerShape(8.dp)) 39 | .clickable { onClick() } 40 | .background(if (isSelected) selectedBgColor else MaterialTheme.colorScheme.primaryContainer), 41 | contentAlignment = Alignment.Center 42 | ) { 43 | Text( 44 | text = title, 45 | style = h3TextStyle, 46 | color = if (isSelected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onBackground, 47 | modifier = Modifier.padding(24.dp, 8.dp) 48 | ) 49 | } 50 | if (isSelected) { 51 | 52 | val animValue = remember { Animatable(initialValue = 0f) } 53 | 54 | LaunchedEffect(Unit) { 55 | animValue.animateTo(1f, tween(300)) 56 | } 57 | 58 | Box( 59 | modifier = Modifier 60 | .width(40.dp * animValue.value) 61 | .height(4.dp) 62 | .background(selectedBgColor, RoundedCornerShape(8.dp)) 63 | ) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/settings/common/TopLanguage.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.settings.common 2 | 3 | enum class TopLanguage(val languageCode: String, val emoji: String) { 4 | CHINESE("zh", "\uD83C\uDDE8\uD83C\uDDF3"), 5 | DANISH("da", "\uD83C\uDDE9\uD83C\uDDF0"), 6 | DUTCH("nl", "\uD83C\uDDF3\uD83C\uDDF1"), 7 | ENGLISH("en", "\uD83C\uDDEC\uD83C\uDDE7"), 8 | FRENCH("fr", "\uD83C\uDDEB\uD83C\uDDF7"), 9 | GERMAN("de", "\uD83C\uDDE9\uD83C\uDDEA"), 10 | ITALIAN("it", "\uD83C\uDDEE\uD83C\uDDF9"), 11 | JAPANESE("ja", "\uD83C\uDDEF\uD83C\uDDF5"), 12 | NORWEGIAN("no", "\uD83C\uDDF3\uD83C\uDDF4"), 13 | POLISH("pl", "\uD83C\uDDF5\uD83C\uDDF1"), 14 | PERSIAN("fa", "\uD83C\uDDE7\uD83C\uDDEB"), 15 | PORTUGUESE("pt", "\uD83C\uDDF5\uD83C\uDDF9"), 16 | RUSSIAN("ru", "\uD83C\uDDF7\uD83C\uDDFA"), 17 | SPANISH("es", "\uD83C\uDDEA\uD83C\uDDF8"), 18 | TURKISH("tr", "\uD83C\uDDF9\uD83C\uDDF7"), 19 | UKRAINIAN("uk", "\uD83C\uDDFA\uD83C\uDDE6"), 20 | VIETNAMESE("vi", "\uD83C\uDDFB\uD83C\uDDF3"); 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/settings/components/LanguageOptionComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.settings.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.width 9 | import androidx.compose.foundation.rememberScrollState 10 | import androidx.compose.foundation.verticalScroll 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.RadioButton 13 | import androidx.compose.material3.RadioButtonDefaults 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.getValue 17 | import androidx.compose.runtime.mutableStateOf 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.runtime.setValue 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.res.stringResource 23 | import androidx.compose.ui.tooling.preview.Preview 24 | import androidx.compose.ui.unit.dp 25 | import com.vishal2376.snaptick.R 26 | import com.vishal2376.snaptick.presentation.common.h2TextStyle 27 | import com.vishal2376.snaptick.presentation.common.taskTextStyle 28 | import com.vishal2376.snaptick.presentation.settings.common.TopLanguage 29 | import com.vishal2376.snaptick.ui.theme.Blue 30 | 31 | @Composable 32 | fun LanguageOptionComponent(defaultLanguage: String, onSelect: (String) -> Unit) { 33 | var selectedLanguage by remember { mutableStateOf(TopLanguage.ENGLISH) } 34 | 35 | for (language in TopLanguage.entries) { 36 | if (language.languageCode == defaultLanguage) { 37 | selectedLanguage = language 38 | break 39 | } 40 | } 41 | 42 | Column(horizontalAlignment = Alignment.CenterHorizontally) { 43 | Text( 44 | text = stringResource(R.string.select_language), 45 | style = h2TextStyle, 46 | color = MaterialTheme.colorScheme.onBackground, 47 | ) 48 | Spacer(modifier = Modifier.height(8.dp)) 49 | Column(Modifier.verticalScroll(rememberScrollState())) { 50 | TopLanguage.entries.forEach { language -> 51 | Row( 52 | modifier = Modifier.fillMaxWidth(), 53 | verticalAlignment = Alignment.CenterVertically 54 | ) { 55 | RadioButton( 56 | selected = selectedLanguage == language, 57 | onClick = { 58 | selectedLanguage = language 59 | onSelect(selectedLanguage.languageCode) 60 | }, 61 | colors = RadioButtonDefaults.colors(selectedColor = Blue) 62 | ) 63 | Text( 64 | text = language.emoji, 65 | style = taskTextStyle, 66 | color = MaterialTheme.colorScheme.onPrimaryContainer 67 | ) 68 | Spacer(modifier = Modifier.width(8.dp)) 69 | Text( 70 | text = language.name, 71 | style = taskTextStyle, 72 | color = MaterialTheme.colorScheme.onPrimaryContainer 73 | ) 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | @Preview 81 | @Composable 82 | fun LanguageOptionComponentPreview() { 83 | LanguageOptionComponent("en", {}) 84 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/settings/components/SleepTimeOptionComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.settings.components 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.res.stringResource 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | import com.commandiron.wheel_picker_compose.WheelTimePicker 17 | import com.commandiron.wheel_picker_compose.core.TimeFormat 18 | import com.vishal2376.snaptick.R 19 | import com.vishal2376.snaptick.presentation.common.h1TextStyle 20 | import com.vishal2376.snaptick.presentation.common.h2TextStyle 21 | import com.vishal2376.snaptick.presentation.common.infoDescTextStyle 22 | import com.vishal2376.snaptick.presentation.common.taskDescTextStyle 23 | import java.time.LocalTime 24 | import java.time.format.DateTimeFormatter 25 | 26 | @Composable 27 | fun SleepTimeOptionComponent(defaultSleepTime: LocalTime, onSelect: (LocalTime) -> Unit) { 28 | Column( 29 | modifier = Modifier.fillMaxWidth(), 30 | horizontalAlignment = Alignment.CenterHorizontally, 31 | verticalArrangement = Arrangement.spacedBy(8.dp) 32 | ) { 33 | val minSleepTime = LocalTime.of(21, 0) 34 | val dtf = DateTimeFormatter.ofPattern("hh:mm a") 35 | 36 | Text( 37 | text = stringResource(R.string.set_sleep_time), 38 | style = h1TextStyle, 39 | color = MaterialTheme.colorScheme.onBackground, 40 | ) 41 | Text( 42 | text = stringResource( 43 | R.string.min_max, 44 | minSleepTime.format(dtf), 45 | LocalTime.of(23, 59).format(dtf) 46 | ), 47 | style = infoDescTextStyle, 48 | color = MaterialTheme.colorScheme.onPrimaryContainer, 49 | ) 50 | Spacer(modifier = Modifier.height(16.dp)) 51 | WheelTimePicker( 52 | timeFormat = TimeFormat.AM_PM, 53 | startTime = defaultSleepTime, 54 | minTime = minSleepTime, 55 | maxTime = LocalTime.MAX, 56 | textColor = MaterialTheme.colorScheme.onBackground, 57 | onSnappedTime = { 58 | val sleepTime = LocalTime.of(it.hour, it.minute) 59 | onSelect(sleepTime) 60 | } 61 | ) 62 | } 63 | } 64 | 65 | @Preview 66 | @Composable 67 | fun SleepTimeOptionComponentPreview() { 68 | SleepTimeOptionComponent(defaultSleepTime = LocalTime.MAX, onSelect = {}) 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/presentation/settings/components/SwipeActionOptionComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.presentation.settings.components 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.res.stringResource 14 | import androidx.compose.ui.unit.dp 15 | import com.vishal2376.snaptick.R 16 | import com.vishal2376.snaptick.presentation.common.SwipeBehavior 17 | import com.vishal2376.snaptick.presentation.common.h2TextStyle 18 | import com.vishal2376.snaptick.presentation.settings.common.ToggleOptions 19 | import com.vishal2376.snaptick.ui.theme.LightGreen 20 | import com.vishal2376.snaptick.ui.theme.Red 21 | 22 | @Composable 23 | fun SwipeActionOptionComponent( 24 | selected: SwipeBehavior, 25 | onSelect: (SwipeBehavior) -> Unit 26 | ) { 27 | val selectedOptionColor = when (selected) { 28 | SwipeBehavior.NONE -> MaterialTheme.colorScheme.primary 29 | SwipeBehavior.DELETE -> Red 30 | SwipeBehavior.COMPLETE -> LightGreen 31 | } 32 | 33 | Column( 34 | modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), 35 | horizontalAlignment = Alignment.CenterHorizontally, 36 | verticalArrangement = Arrangement.spacedBy(16.dp) 37 | ) { 38 | Text( 39 | text = stringResource(R.string.choose_swipe_action), 40 | style = h2TextStyle, 41 | color = MaterialTheme.colorScheme.onBackground 42 | ) 43 | 44 | Row( 45 | modifier = Modifier 46 | .fillMaxWidth() 47 | .padding(horizontal = 16.dp), 48 | horizontalArrangement = Arrangement.SpaceAround, 49 | ) { 50 | ToggleOptions( 51 | title = stringResource(R.string.none), 52 | selectedBgColor = selectedOptionColor, 53 | isSelected = selected == SwipeBehavior.NONE 54 | ) { 55 | onSelect(SwipeBehavior.NONE) 56 | } 57 | ToggleOptions( 58 | title = stringResource(R.string.delete), 59 | selectedBgColor = selectedOptionColor, 60 | isSelected = selected == SwipeBehavior.DELETE 61 | ) { 62 | onSelect(SwipeBehavior.DELETE) 63 | } 64 | ToggleOptions( 65 | title = stringResource(R.string.complete), 66 | selectedBgColor = selectedOptionColor, 67 | isSelected = selected == SwipeBehavior.COMPLETE 68 | ) { 69 | onSelect(SwipeBehavior.COMPLETE) 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Blue500 = Color(0xFF161A30) 6 | val Blue200 = Color(0xFF31304D) 7 | 8 | val Black500 = Color(0xFF000000) 9 | val Black200 = Color(0xFF252526) 10 | 11 | val White500 = Color(0xFFEAF3FF) 12 | val White200 = Color(0xFFD9E9FF) 13 | 14 | val Red = Color(0xFFEB8080) 15 | val Blue = Color(0xFF83BCFF) 16 | val Yellow = Color(0xFFFF9737) 17 | 18 | val LightGray = Color(0xFFCCD2D8) 19 | val DarkGray = Color(0xFF4E4E4E) 20 | 21 | val LightGreen = Color(0xFFC7E9A7) 22 | val DarkGreen = Color(0xFF64A625) 23 | 24 | // priority colors 25 | val priorityColors = listOf( 26 | LightGray, 27 | Yellow, 28 | Red 29 | ) 30 | 31 | // pie chart colors 32 | val pieChartColors = listOf( 33 | Color(0xFFFFA502), 34 | Color(0xFF70A1FF), 35 | Color(0xFFFF4757), 36 | Color(0xFF2ED573), 37 | Color(0xFFFF7F50), 38 | Color(0xFFA4B0BE), 39 | Color(0xFFFF6B81), 40 | Color(0xFF747D8C), 41 | Color(0xFFECCC68), 42 | Color(0xFF1E90FF), 43 | Color(0xFFCED6E0), 44 | Color(0xFF7BED9F), 45 | Color(0xFF2F3542), 46 | Color(0xFFFF6348), 47 | Color(0xFFDCDDDF), 48 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 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.runtime.SideEffect 12 | import androidx.compose.ui.graphics.toArgb 13 | import androidx.compose.ui.platform.LocalContext 14 | import androidx.compose.ui.platform.LocalView 15 | import androidx.core.view.WindowCompat 16 | import com.vishal2376.snaptick.presentation.common.AppTheme 17 | 18 | val DarkColorScheme = darkColorScheme( 19 | primary = Blue, 20 | onPrimary = Blue500, 21 | background = Blue500, 22 | onBackground = White500, 23 | primaryContainer = Blue200, 24 | onPrimaryContainer = LightGray, 25 | ) 26 | 27 | val AmoledDarkColorScheme = darkColorScheme( 28 | primary = Blue, 29 | onPrimary = Blue500, 30 | background = Black500, 31 | onBackground = White500, 32 | primaryContainer = Black200, 33 | onPrimaryContainer = LightGray, 34 | ) 35 | 36 | private val LightColorScheme = lightColorScheme( 37 | primary = Blue, 38 | onPrimary = Blue500, 39 | background = White500, 40 | onBackground = Black500, 41 | primaryContainer = White200, 42 | onPrimaryContainer = DarkGray, 43 | ) 44 | 45 | 46 | @Composable 47 | fun SnaptickTheme( 48 | theme: AppTheme = AppTheme.Amoled, 49 | dynamicColor: Boolean = false, 50 | content: @Composable () -> Unit 51 | ) { 52 | val colorScheme = when { 53 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 54 | val context = LocalContext.current 55 | if (theme == AppTheme.Light) 56 | dynamicLightColorScheme(context) 57 | else 58 | dynamicDarkColorScheme(context) 59 | } 60 | 61 | theme == AppTheme.Light -> LightColorScheme 62 | theme == AppTheme.Dark -> DarkColorScheme 63 | theme == AppTheme.Amoled -> AmoledDarkColorScheme 64 | else -> DarkColorScheme 65 | } 66 | val view = LocalView.current 67 | if (!view.isInEditMode) { 68 | SideEffect { 69 | val window = (view.context as Activity).window 70 | window.statusBarColor = colorScheme.background.toArgb() 71 | WindowCompat.getInsetsController( 72 | window, 73 | view 74 | ).isAppearanceLightStatusBars = theme == AppTheme.Light 75 | } 76 | } 77 | 78 | MaterialTheme( 79 | colorScheme = colorScheme, 80 | typography = Typography, 81 | content = content 82 | ) 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) /* Other default text styles to override 18 | titleLarge = TextStyle( 19 | fontFamily = FontFamily.Default, 20 | fontWeight = FontWeight.Normal, 21 | fontSize = 22.sp, 22 | lineHeight = 28.sp, 23 | letterSpacing = 0.sp 24 | ), 25 | labelSmall = TextStyle( 26 | fontFamily = FontFamily.Default, 27 | fontWeight = FontWeight.Medium, 28 | fontSize = 11.sp, 29 | lineHeight = 16.sp, 30 | letterSpacing = 0.5.sp 31 | ) 32 | */ 33 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/AudioUtil.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import android.content.Context 4 | import android.media.MediaPlayer 5 | import androidx.annotation.RawRes 6 | import com.vishal2376.snaptick.R 7 | 8 | private fun playMediaSound(context: Context, @RawRes soundResId: Int) { 9 | val mediaPlayer = MediaPlayer.create(context, soundResId) 10 | mediaPlayer?.apply { 11 | setOnCompletionListener { 12 | release() 13 | } 14 | start() 15 | } 16 | } 17 | 18 | fun playSound(context: Context, soundEvent: SoundEvent) { 19 | val soundResId = when (soundEvent) { 20 | SoundEvent.TASK_ADDED -> R.raw.task_added 21 | SoundEvent.TASK_COMPLETED -> R.raw.task_completed 22 | SoundEvent.TASK_DELETED -> R.raw.task_deleted 23 | } 24 | playMediaSound(context, soundResId) 25 | } 26 | 27 | enum class SoundEvent { 28 | TASK_ADDED, 29 | TASK_COMPLETED, 30 | TASK_DELETED, 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/BackupManager.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import com.google.gson.Gson 7 | import com.google.gson.GsonBuilder 8 | import com.vishal2376.snaptick.domain.model.BackupData 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.withContext 12 | import java.time.LocalDate 13 | import java.time.LocalDateTime 14 | import java.time.LocalTime 15 | import javax.inject.Inject 16 | import javax.inject.Singleton 17 | 18 | @Singleton 19 | class BackupManager @Inject constructor( 20 | @ApplicationContext val context: Context 21 | ) { 22 | private val timestamp = LocalDate.now() 23 | private val backupFileName = "Snaptick_Backup_$timestamp.json" 24 | private val gson: Gson = GsonBuilder() 25 | .registerTypeAdapter(LocalDate::class.java, LocalDateAdapter()) 26 | .registerTypeAdapter(LocalTime::class.java, LocalTimeAdapter()) 27 | .create() 28 | 29 | suspend fun createBackup(uri: Uri, data: BackupData): Boolean { 30 | return withContext(Dispatchers.IO) { 31 | try { 32 | context.contentResolver.openOutputStream(uri)?.use { outputStream -> 33 | val jsonData = gson.toJson(data) 34 | outputStream.write(jsonData.toByteArray()) 35 | } 36 | true 37 | } catch (e: Exception) { 38 | e.printStackTrace() 39 | false 40 | } 41 | } 42 | } 43 | 44 | suspend fun loadBackup(uri: Uri): BackupData? { 45 | return withContext(Dispatchers.IO) { 46 | try { 47 | context.contentResolver.openInputStream(uri)?.use { inputStream -> 48 | val jsonData = inputStream.bufferedReader().use { it.readText() } 49 | gson.fromJson(jsonData, BackupData::class.java) 50 | } 51 | } catch (e: Exception) { 52 | e.printStackTrace() 53 | null 54 | } 55 | } 56 | } 57 | 58 | fun getBackupFilePickerIntent(): Intent { 59 | val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { 60 | addCategory(Intent.CATEGORY_OPENABLE) 61 | type = "application/json" 62 | putExtra(Intent.EXTRA_TITLE, backupFileName) 63 | } 64 | return intent 65 | } 66 | 67 | fun getLoadBackupFilePickerIntent(): Intent { 68 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { 69 | addCategory(Intent.CATEGORY_OPENABLE) 70 | type = "application/json" 71 | } 72 | return intent 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | object Constants { 4 | 5 | const val EMAIL = "vishalsingh2376@gmail.com" 6 | const val GITHUB = "https://github.com/vishal2376" 7 | const val LINKEDIN = "https://www.linkedin.com/in/vishal2376" 8 | const val TWITTER = "https://twitter.com/vishal2376" 9 | const val INSTAGRAM = "https://www.instagram.com/vishal_2376/" 10 | 11 | const val LIST_ANIMATION_DELAY = 100 12 | const val MIN_ALLOWED_DURATION = 5L // 5 min 13 | const val MIN_VALID_POMODORO_SESSION = 1L // 1 min 14 | 15 | // DATA STORE KEYS 16 | const val SETTINGS_KEY = "settings_key" 17 | 18 | // WORK MANAGER DATA KEYS 19 | const val TASK_ID = "task_id" 20 | const val TASK_UUID = "task_uuid" 21 | const val TASK_TITLE = "task_title" 22 | const val TASK_TIME = "task_time" 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/DummyTasks.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | import java.time.LocalDate 5 | import java.time.LocalTime 6 | import kotlin.random.Random 7 | 8 | object DummyTasks { 9 | 10 | val dummyTasks = generateDummyTasks() 11 | 12 | private fun generateDummyTasks(count: Int = 5): List { 13 | val tasks = mutableListOf() 14 | 15 | for (i in 1..count) { 16 | val task = Task( 17 | id = i, 18 | uuid = "$i", 19 | title = "Task $i", 20 | isCompleted = Random.nextBoolean(), 21 | startTime = LocalTime.now(), 22 | endTime = LocalTime.now().plusHours(Random.nextLong(1, 4)), 23 | reminder = Random.nextBoolean(), 24 | isRepeated = Random.nextBoolean(), 25 | repeatWeekdays = "", 26 | pomodoroTimer = -1, 27 | priority = Random.nextInt(3), 28 | date = LocalDate.now() 29 | ) 30 | tasks.add(task) 31 | } 32 | return tasks 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/GsonAdapters.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import com.google.gson.TypeAdapter 4 | import com.google.gson.stream.JsonReader 5 | import com.google.gson.stream.JsonWriter 6 | import java.time.LocalDate 7 | import java.time.LocalTime 8 | import java.time.format.DateTimeFormatter 9 | 10 | class LocalDateAdapter : TypeAdapter() { 11 | private val formatter = DateTimeFormatter.ISO_LOCAL_DATE 12 | 13 | override fun write(out: JsonWriter, value: LocalDate) { 14 | out.value(value.format(formatter)) 15 | } 16 | 17 | override fun read(input: JsonReader): LocalDate { 18 | return LocalDate.parse(input.nextString(), formatter) 19 | } 20 | } 21 | 22 | class LocalTimeAdapter : TypeAdapter() { 23 | private val formatter = DateTimeFormatter.ISO_LOCAL_TIME 24 | 25 | override fun write(out: JsonWriter, value: LocalTime) { 26 | out.value(value.format(formatter)) 27 | } 28 | 29 | override fun read(input: JsonReader): LocalTime { 30 | return LocalTime.parse(input.nextString(), formatter) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/LocaleHelper.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import android.content.Context 4 | import android.content.res.Configuration 5 | import java.util.Locale 6 | 7 | object LocaleHelper { 8 | 9 | fun setLocale(context: Context, language: String = "en"):Context { 10 | val locale = Locale(language) 11 | Locale.setDefault(locale) 12 | 13 | val resources = context.resources 14 | val configuration = Configuration(resources.configuration) 15 | configuration.setLocale(locale) 16 | 17 | return context.createConfigurationContext(configuration) 18 | 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/NotificationHelper.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.app.PendingIntent 6 | import android.content.Context 7 | import android.content.Context.NOTIFICATION_SERVICE 8 | import android.content.Intent 9 | import androidx.core.app.NotificationCompat 10 | import com.vishal2376.snaptick.MainActivity 11 | import com.vishal2376.snaptick.R 12 | 13 | const val NOTIFICATION = "Notification" 14 | const val CHANNEL_ID = "snaptick-notification" 15 | const val CHANNEL_NAME = "Task Reminder" 16 | 17 | class NotificationHelper(private val context: Context) { 18 | 19 | private val notificationManager = 20 | context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager 21 | 22 | fun showNotification(taskId: Int, taskTitle: String, taskTime: String) { 23 | val intent = Intent(context, MainActivity::class.java).apply { 24 | flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK 25 | } 26 | 27 | val pendingIntent = PendingIntent.getActivity( 28 | context, 29 | taskId, 30 | intent, 31 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 32 | ) 33 | 34 | val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID) 35 | .setContentTitle(taskTitle) 36 | .setContentText(taskTime) 37 | .setSmallIcon(R.drawable.ic_clock) 38 | .setStyle(NotificationCompat.BigTextStyle().bigText(taskTime)) 39 | .setDefaults(NotificationCompat.DEFAULT_ALL) 40 | .setContentIntent(pendingIntent) 41 | .setAutoCancel(true) 42 | 43 | notificationManager.notify(taskId, notificationBuilder.build()) 44 | } 45 | 46 | fun createNotificationChannel() { 47 | val mChannel = 48 | NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH) 49 | notificationManager.createNotificationChannel(mChannel) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import android.content.Context 4 | import android.content.Context.VIBRATOR_SERVICE 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.VibrationEffect 8 | import android.os.Vibrator 9 | import android.widget.Toast 10 | import com.vishal2376.snaptick.R 11 | import java.time.LocalTime 12 | 13 | fun vibrateDevice( 14 | context: Context, 15 | duration: Long = 500, 16 | vibrationEffect: Int = VibrationEffect.DEFAULT_AMPLITUDE 17 | ) { 18 | val vibrator = context.getSystemService(VIBRATOR_SERVICE) as Vibrator? 19 | 20 | if (vibrator?.hasVibrator() == true) { 21 | vibrator.vibrate(VibrationEffect.createOneShot(duration, vibrationEffect)) 22 | } 23 | } 24 | 25 | fun updateLocale(context: Context, selectedLocale: String) { 26 | val updatedContext = LocaleHelper.setLocale(context, selectedLocale) 27 | context.resources.updateConfiguration( 28 | updatedContext.resources.configuration, 29 | updatedContext.resources.displayMetrics 30 | ) 31 | } 32 | 33 | fun showToast(context: Context, message: String, duration: Int = Toast.LENGTH_LONG) { 34 | Toast.makeText(context, message, duration).show() 35 | } 36 | 37 | 38 | fun getFormattedDuration( 39 | startTime: LocalTime, 40 | endTime: LocalTime 41 | ): String { 42 | val taskDuration = endTime.toSecondOfDay() - startTime.toSecondOfDay() 43 | 44 | val hours = taskDuration / 3600 45 | val minutes = (taskDuration % 3600) / 60 46 | 47 | val hoursString = if (hours == 1) "hour" else "hours" 48 | 49 | return when { 50 | hours > 0 && minutes > 0 -> String.format("%dh %02dm", hours, minutes) 51 | hours > 0 -> String.format("%d $hoursString", hours) 52 | else -> String.format("%dmin", minutes) 53 | } 54 | 55 | } 56 | 57 | fun getFreeTime(totalDuration: Long, sleepTime: LocalTime): String { 58 | val maxTime = sleepTime.toSecondOfDay() 59 | val currentTime = LocalTime.now().toSecondOfDay() 60 | 61 | val totalFreeDuration = maxTime - currentTime - totalDuration 62 | 63 | val hours = (totalFreeDuration / 3600).toInt() 64 | val minutes = ((totalFreeDuration % 3600) / 60).toInt() 65 | 66 | 67 | val hoursString = if (hours == 1) "hour" else "hours" 68 | 69 | return when { 70 | hours > 0 && minutes > 0 -> String.format("%dh %02dm", hours, minutes) 71 | hours > 0 -> String.format("%d $hoursString", hours) 72 | else -> String.format("%d min", minutes) 73 | } 74 | } 75 | 76 | fun openMail(context: Context, title: String) { 77 | val subject = "${context.getString(R.string.app_name)}: $title" 78 | val uriBuilder = StringBuilder("mailto:" + Uri.encode(Constants.EMAIL)) 79 | uriBuilder.append("?subject=" + Uri.encode(subject)) 80 | val uriString = uriBuilder.toString() 81 | 82 | val intentTitle = "Send $title" 83 | val intent = Intent(Intent.ACTION_SENDTO, Uri.parse(uriString)) 84 | context.startActivity(Intent.createChooser(intent, intentTitle)) 85 | } 86 | 87 | fun openUrl(context: Context, urlString: String) { 88 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(urlString)) 89 | context.startActivity(intent) 90 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/util/Validation.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.util 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | import java.time.LocalDate 5 | import java.time.LocalTime 6 | 7 | fun checkValidTask( 8 | task: Task, 9 | totalTasksDuration: Long = 0, 10 | isTaskAllDay: Boolean = false, 11 | sleepTime: LocalTime = LocalTime.MAX 12 | ): Pair { 13 | val maxTime = sleepTime.toSecondOfDay() 14 | val currentTime = LocalTime.now().toSecondOfDay() 15 | val freeTime = maxTime - currentTime - totalTasksDuration 16 | val formattedFreeTime = getFreeTime(totalTasksDuration, sleepTime) 17 | 18 | val currentDuration = task.getDuration(checkPastTask = true) 19 | val startTimeSec = task.startTime.toSecondOfDay() 20 | 21 | if (task.title.trim().isEmpty()) { 22 | return Pair(false, "Title can't be empty") 23 | } 24 | 25 | if ((task.getDuration() < Constants.MIN_ALLOWED_DURATION * 60) && !isTaskAllDay) { 26 | return Pair(false, "Task should be at least ${Constants.MIN_ALLOWED_DURATION} minutes.") 27 | } 28 | 29 | if (task.date < LocalDate.now()) { 30 | return Pair(false, "Past dates are not allowed") 31 | } 32 | 33 | if (task.date > LocalDate.now()) { 34 | return Pair(true, "Future Task") 35 | } 36 | 37 | if (currentDuration >= freeTime) { 38 | return Pair(false, "Invalid Duration! You have only $formattedFreeTime remaining.") 39 | } 40 | 41 | // if (task.reminder) { 42 | // if (startTimeSec < currentTime && !task.isRepeated) { 43 | // return Pair(false, "Cannot set a reminder for past time") 44 | // } 45 | // } 46 | return Pair(true, "Valid Task") 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/OnTaskClickedCallback.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget 2 | 3 | import android.content.Context 4 | import androidx.glance.GlanceId 5 | import androidx.glance.action.ActionParameters 6 | import androidx.glance.appwidget.action.ActionCallback 7 | import com.vishal2376.snaptick.data.repositories.TaskRepository 8 | import com.vishal2376.snaptick.domain.interactor.AppWidgetInteractor 9 | import dagger.hilt.EntryPoint 10 | import dagger.hilt.EntryPoints 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | 14 | class OnTaskClickedCallback : ActionCallback { 15 | 16 | @EntryPoint 17 | @InstallIn(SingletonComponent::class) 18 | interface GlanceActionEntryPoint { 19 | 20 | fun taskRepository(): TaskRepository 21 | 22 | fun glanceInterceptor(): AppWidgetInteractor 23 | 24 | } 25 | 26 | override suspend fun onAction( 27 | context: Context, 28 | glanceId: GlanceId, 29 | parameters: ActionParameters 30 | ) { 31 | val taskId: Int = parameters[parameterTaskId] ?: -1 32 | 33 | val entryPoint = 34 | EntryPoints.get(context.applicationContext, GlanceActionEntryPoint::class.java) 35 | val taskRepository = entryPoint.taskRepository() 36 | 37 | taskRepository.getTaskById(taskId)?.let { 38 | val updatedTask = it.copy(isCompleted = !it.isCompleted) 39 | // update the task 40 | taskRepository.updateTask(updatedTask) 41 | 42 | entryPoint.glanceInterceptor().enqueueWidgetDataWorker() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/SnaptickWidget.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.unit.dp 8 | import androidx.glance.GlanceId 9 | import androidx.glance.GlanceModifier 10 | import androidx.glance.ImageProvider 11 | import androidx.glance.action.ActionParameters 12 | import androidx.glance.action.actionParametersOf 13 | import androidx.glance.appwidget.GlanceAppWidget 14 | import androidx.glance.appwidget.action.actionRunCallback 15 | import androidx.glance.appwidget.appWidgetBackground 16 | import androidx.glance.appwidget.provideContent 17 | import androidx.glance.background 18 | import androidx.glance.currentState 19 | import androidx.glance.layout.fillMaxSize 20 | import androidx.glance.layout.padding 21 | import androidx.glance.state.GlanceStateDefinition 22 | import com.vishal2376.snaptick.R 23 | import com.vishal2376.snaptick.widget.components.SnaptickWidgetTheme 24 | import com.vishal2376.snaptick.widget.components.WidgetTasks 25 | 26 | val parameterTaskId = ActionParameters.Key("task_id") 27 | 28 | object SnaptickWidget : GlanceAppWidget() { 29 | 30 | override val stateDefinition: GlanceStateDefinition 31 | get() = SnaptickWidgetStateDefinition 32 | 33 | 34 | override suspend fun provideGlance(context: Context, id: GlanceId) { 35 | 36 | provideContent { 37 | 38 | val state = currentState() 39 | 40 | val tasks by remember(state) { 41 | derivedStateOf{ state.tasks } 42 | } 43 | 44 | SnaptickWidgetTheme { 45 | WidgetTasks( 46 | tasks = tasks, 47 | onTaskClick = { taskId -> 48 | actionRunCallback( 49 | parameters = actionParametersOf(parameterTaskId to taskId) 50 | ) 51 | }, 52 | modifier = GlanceModifier 53 | .appWidgetBackground() 54 | .fillMaxSize() 55 | .background(ImageProvider(R.drawable.bg_round_primary)) 56 | .padding(16.dp) 57 | ) 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/SnaptickWidgetReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.glance.appwidget.GlanceAppWidget 6 | import androidx.glance.appwidget.GlanceAppWidgetReceiver 7 | import com.vishal2376.snaptick.domain.interactor.AppWidgetInteractor 8 | import dagger.hilt.android.AndroidEntryPoint 9 | import javax.inject.Inject 10 | 11 | private const val LOGGER_TAG = "GLANCE_APP_WIDGET" 12 | 13 | @AndroidEntryPoint 14 | class SnaptickWidgetReceiver : GlanceAppWidgetReceiver() { 15 | 16 | @Inject 17 | lateinit var interceptor: AppWidgetInteractor 18 | 19 | override val glanceAppWidget: GlanceAppWidget 20 | get() = SnaptickWidget 21 | 22 | override fun onEnabled(context: Context) { 23 | super.onEnabled(context) 24 | Log.d(LOGGER_TAG,"ONE_TIME_WORKER_ENQUEUED") 25 | // enqueue the current worker for current update 26 | interceptor.enqueueWidgetDataWorker() 27 | Log.d(LOGGER_TAG, "PERIODIC_WORKER_ENQUEUED") 28 | // enqueue the periodic worker 29 | interceptor.enqueuePeriodicWidgetUpdateWorker() 30 | } 31 | 32 | override fun onDisabled(context: Context?) { 33 | Log.d(LOGGER_TAG, "PERIODIC_WORKER_CANCELED") 34 | // cancel worker on remove 35 | interceptor.cancelPeriodicWidgetUpdateWorker() 36 | super.onDisabled(context) 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/SnaptickWidgetState.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.vishal2376.snaptick.widget.model.WidgetTaskModel 5 | 6 | /** 7 | * [SnaptickWidgetState] presents the widget state, 8 | * @param tasks [List] of [WidgetTaskModel] the actual tasks to be shown in the widget 9 | */ 10 | data class SnaptickWidgetState( 11 | @SerializedName("tasks") 12 | val tasks: List = emptyList() 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/SnaptickWidgetStateDefinition.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.CorruptionException 5 | import androidx.datastore.core.DataStore 6 | import androidx.datastore.core.Serializer 7 | import androidx.datastore.dataStore 8 | import androidx.datastore.dataStoreFile 9 | import androidx.glance.state.GlanceStateDefinition 10 | import com.google.gson.GsonBuilder 11 | import com.google.gson.JsonParseException 12 | import com.vishal2376.snaptick.widget.model.WidgetTaskModel 13 | import com.vishal2376.snaptick.widget.util.LocalTimeGsonSerializer 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.withContext 16 | import java.io.File 17 | import java.io.InputStream 18 | import java.io.OutputStream 19 | import java.time.LocalTime 20 | 21 | 22 | object SnaptickWidgetStateDefinition 23 | : GlanceStateDefinition { 24 | 25 | private const val FILE_NAME = "_WIDGET_DATA_DEFINITION" 26 | 27 | private val Context.widgetData by dataStore(FILE_NAME, TaskSerializers) 28 | 29 | override suspend fun getDataStore(context: Context, fileKey: String) 30 | : DataStore = context.widgetData 31 | 32 | override fun getLocation(context: Context, fileKey: String) 33 | : File = context.dataStoreFile(FILE_NAME) 34 | 35 | 36 | suspend fun updateData(context: Context, newTasks: List) = 37 | withContext(Dispatchers.IO) { 38 | context.widgetData.updateData { state -> state.copy(tasks = newTasks) } 39 | } 40 | 41 | 42 | private object TaskSerializers : Serializer { 43 | 44 | private val gson = GsonBuilder() 45 | .serializeNulls() 46 | .registerTypeAdapter(LocalTime::class.java, LocalTimeGsonSerializer) 47 | .create() 48 | 49 | 50 | override val defaultValue: SnaptickWidgetState 51 | get() = SnaptickWidgetState() 52 | 53 | override suspend fun readFrom(input: InputStream): SnaptickWidgetState { 54 | return try { 55 | input.use { stream -> 56 | val reader = stream.reader() 57 | gson.fromJson(reader, SnaptickWidgetState::class.java) 58 | } 59 | } catch (e: JsonParseException) { 60 | e.printStackTrace() 61 | throw CorruptionException("Could not read JSON: ${e.message}", e) 62 | } 63 | } 64 | 65 | override suspend fun writeTo(t: SnaptickWidgetState, output: OutputStream) { 66 | output.use { stream -> 67 | try { 68 | val writer = stream.bufferedWriter() 69 | val jsonData = gson.toJson(t, SnaptickWidgetState::class.java) 70 | writer.write(jsonData) 71 | } catch (e: JsonParseException) { 72 | throw CorruptionException("Could not convert to JSON: ${e.message}", e) 73 | } 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/components/SnaptickWidgetTheme.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.glance.GlanceComposable 5 | import androidx.glance.GlanceTheme 6 | import androidx.glance.material3.ColorProviders 7 | import com.vishal2376.snaptick.ui.theme.DarkColorScheme 8 | 9 | @Composable 10 | @GlanceComposable 11 | fun SnaptickWidgetTheme( 12 | content: @Composable () -> Unit 13 | ) { 14 | GlanceTheme( 15 | colors = ColorProviders(DarkColorScheme), 16 | content = content 17 | ) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/components/WidgetTaskComponent.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import androidx.compose.ui.unit.dp 6 | import androidx.compose.ui.unit.sp 7 | import androidx.glance.GlanceModifier 8 | import androidx.glance.Image 9 | import androidx.glance.ImageProvider 10 | import androidx.glance.action.Action 11 | import androidx.glance.action.clickable 12 | import androidx.glance.background 13 | import androidx.glance.layout.Alignment 14 | import androidx.glance.layout.Column 15 | import androidx.glance.layout.Row 16 | import androidx.glance.layout.Spacer 17 | import androidx.glance.layout.height 18 | import androidx.glance.layout.padding 19 | import androidx.glance.layout.size 20 | import androidx.glance.layout.width 21 | import androidx.glance.text.Text 22 | import androidx.glance.text.TextStyle 23 | import androidx.glance.unit.ColorProvider 24 | import com.vishal2376.snaptick.R 25 | import com.vishal2376.snaptick.ui.theme.LightGray 26 | import com.vishal2376.snaptick.ui.theme.White500 27 | import com.vishal2376.snaptick.widget.model.WidgetTaskModel 28 | 29 | @Composable 30 | fun WidgetTaskComponent( 31 | task: WidgetTaskModel, 32 | onClick: Action, 33 | modifier: GlanceModifier = GlanceModifier 34 | ) { 35 | 36 | val taskBackground = remember(task) { 37 | when (task.priority) { 38 | 1 -> ImageProvider(R.drawable.bg_task_med) 39 | 2 -> ImageProvider(R.drawable.bg_task_high) 40 | else -> ImageProvider(R.drawable.bg_task_low) 41 | } 42 | } 43 | 44 | val checkedImage = remember(task) { 45 | if (task.isCompleted) ImageProvider(R.drawable.ic_check_circle) 46 | else ImageProvider(R.drawable.ic_uncheck_circle) 47 | } 48 | 49 | 50 | Row( 51 | modifier = modifier 52 | .padding(8.dp) 53 | .background(taskBackground), 54 | verticalAlignment = Alignment.CenterVertically 55 | ) { 56 | Image( 57 | provider = checkedImage, 58 | contentDescription = null, 59 | modifier = GlanceModifier 60 | .size(35.dp) 61 | .padding(8.dp) 62 | .clickable(onClick) 63 | ) 64 | Column { 65 | Text( 66 | text = task.title, 67 | style = TextStyle( 68 | color = ColorProvider(color = White500), 69 | fontSize = 16.sp, 70 | ) 71 | ) 72 | Spacer(modifier = GlanceModifier.height(8.dp)) 73 | Row( 74 | verticalAlignment = Alignment.CenterVertically 75 | ) { 76 | Image( 77 | provider = ImageProvider(R.drawable.ic_clock), 78 | contentDescription = null, 79 | modifier = GlanceModifier.size(15.dp) 80 | ) 81 | Spacer(modifier = GlanceModifier.width(4.dp)) 82 | Text( 83 | text = if (task.isAllDayTaskEnabled()) { 84 | "All Day" 85 | } else { 86 | task.getFormattedTime(is24HourFormat = false) 87 | }, 88 | style = TextStyle( 89 | color = ColorProvider(color = LightGray), 90 | fontSize = 12.sp 91 | ) 92 | ) 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/components/WidgetTasks.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.unit.dp 5 | import androidx.compose.ui.unit.sp 6 | import androidx.glance.GlanceComposable 7 | import androidx.glance.GlanceModifier 8 | import androidx.glance.LocalContext 9 | import androidx.glance.action.Action 10 | import androidx.glance.appwidget.lazy.LazyColumn 11 | import androidx.glance.appwidget.lazy.items 12 | import androidx.glance.layout.Column 13 | import androidx.glance.layout.Spacer 14 | import androidx.glance.layout.fillMaxSize 15 | import androidx.glance.layout.fillMaxWidth 16 | import androidx.glance.layout.height 17 | import androidx.glance.text.FontWeight 18 | import androidx.glance.text.Text 19 | import androidx.glance.text.TextStyle 20 | import androidx.glance.unit.ColorProvider 21 | import com.vishal2376.snaptick.R 22 | import com.vishal2376.snaptick.ui.theme.White500 23 | import com.vishal2376.snaptick.widget.model.WidgetTaskModel 24 | 25 | @Composable 26 | @GlanceComposable 27 | fun WidgetTasks( 28 | tasks: List, 29 | onTaskClick: (Int) -> Action, 30 | modifier: GlanceModifier = GlanceModifier 31 | ) { 32 | val context = LocalContext.current 33 | 34 | Column( 35 | modifier = modifier 36 | ) { 37 | Text( 38 | text = context.getString(R.string.today_tasks), 39 | style = TextStyle( 40 | color = ColorProvider(color = White500), 41 | fontSize = 20.sp, 42 | fontWeight = FontWeight.Bold, 43 | ) 44 | ) 45 | Spacer(modifier = GlanceModifier.height(16.dp)) 46 | 47 | when { 48 | tasks.isEmpty() -> WidgetNoTasks(modifier = GlanceModifier.fillMaxSize()) 49 | 50 | else -> LazyColumn(modifier = GlanceModifier.defaultWeight()) { 51 | items( 52 | items = tasks, 53 | itemId = { task -> task.id.toLong() }, 54 | ) { task -> 55 | Column { 56 | WidgetTaskComponent( 57 | task = task, 58 | onClick = onTaskClick(task.id), 59 | modifier = GlanceModifier 60 | .fillMaxWidth() 61 | ) 62 | Spacer(modifier = GlanceModifier.height(4.dp)) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/components/WidgetsNoTasks.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.components 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.unit.sp 5 | import androidx.glance.GlanceModifier 6 | import androidx.glance.LocalContext 7 | import androidx.glance.layout.Alignment 8 | import androidx.glance.layout.Box 9 | import androidx.glance.text.FontWeight 10 | import androidx.glance.text.Text 11 | import androidx.glance.text.TextStyle 12 | import androidx.glance.unit.ColorProvider 13 | import com.vishal2376.snaptick.R 14 | import com.vishal2376.snaptick.ui.theme.LightGray 15 | 16 | @Composable 17 | fun WidgetNoTasks( 18 | modifier: GlanceModifier = GlanceModifier 19 | ) { 20 | val context = LocalContext.current 21 | 22 | Box( 23 | modifier = modifier, 24 | contentAlignment = Alignment.Center 25 | ) { 26 | Text( 27 | text = context.getString(R.string.no_tasks), 28 | style = TextStyle( 29 | color = ColorProvider(color = LightGray), 30 | fontSize = 30.sp, 31 | fontWeight = FontWeight.Bold 32 | ) 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/di/WidgetModule.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.di 2 | 3 | import android.content.Context 4 | import com.vishal2376.snaptick.domain.interactor.AppWidgetInteractor 5 | import com.vishal2376.snaptick.widget.interactor.AppWidgetInteractorImpl 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object WidgetModule { 16 | 17 | @Provides 18 | @Singleton 19 | fun providesGlanceInterceptor( 20 | @ApplicationContext context: Context 21 | ): AppWidgetInteractor = AppWidgetInteractorImpl(context) 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/interactor/AppWidgetInteractorImpl.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.interactor 2 | 3 | import android.content.Context 4 | import com.vishal2376.snaptick.domain.interactor.AppWidgetInteractor 5 | import com.vishal2376.snaptick.widget.worker.WidgetTaskUpdateDataWorker 6 | 7 | class AppWidgetInteractorImpl( 8 | private val context: Context 9 | ) : AppWidgetInteractor { 10 | 11 | override fun enqueueWidgetDataWorker() = 12 | WidgetTaskUpdateDataWorker.enqueueWorker(context) 13 | 14 | override fun cancelWidgetDateWorker() = 15 | WidgetTaskUpdateDataWorker.cancelWorker(context) 16 | 17 | 18 | override fun enqueuePeriodicWidgetUpdateWorker() = 19 | WidgetTaskUpdateDataWorker.enqueuePeriodicWorker(context) 20 | 21 | override fun cancelPeriodicWidgetUpdateWorker() = 22 | WidgetTaskUpdateDataWorker.cancelPeriodicWorker(context) 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/model/TaskToWidgetTaskMapper.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.model 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | 5 | fun Task.toWidgetTask(): WidgetTaskModel = WidgetTaskModel( 6 | id = id, 7 | title = title, 8 | isCompleted = isCompleted, 9 | startTime = startTime, 10 | endTime = endTime, 11 | priority = priority 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/model/WidgetTaskModel.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.model 2 | 3 | import com.vishal2376.snaptick.domain.model.Task 4 | import java.time.LocalTime 5 | import java.time.format.DateTimeFormatter 6 | 7 | /** 8 | * A secondary [Task] model, namely [WidgetTaskModel] as widget doesn't require all the 9 | * information, this [WidgetTaskModel] contains only the fields that are required by the widget. 10 | */ 11 | data class WidgetTaskModel( 12 | val id: Int, 13 | val title: String = "", 14 | val isCompleted: Boolean = false, 15 | val startTime: LocalTime = LocalTime.now(), 16 | val endTime: LocalTime = LocalTime.now(), 17 | val priority: Int = 0, 18 | ) { 19 | 20 | fun getFormattedTime(is24HourFormat: Boolean = false): String { 21 | val dtf = if (is24HourFormat) { 22 | DateTimeFormatter.ofPattern("HH:mm") 23 | } else { 24 | DateTimeFormatter.ofPattern("hh:mm a") 25 | } 26 | val startTimeFormat = startTime.format(dtf) 27 | val endTimeFormat = endTime.format(dtf) 28 | return "$startTimeFormat - $endTimeFormat" 29 | } 30 | 31 | fun isAllDayTaskEnabled(): Boolean = (startTime == endTime) 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/util/LocalTimeGsonSerializer.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.util 2 | 3 | import com.google.gson.GsonBuilder 4 | import com.google.gson.JsonDeserializationContext 5 | import com.google.gson.JsonDeserializer 6 | import com.google.gson.JsonElement 7 | import com.google.gson.JsonPrimitive 8 | import com.google.gson.JsonSerializationContext 9 | import com.google.gson.JsonSerializer 10 | import java.lang.reflect.Type 11 | import java.time.LocalTime 12 | import java.time.format.DateTimeFormatter 13 | 14 | /** 15 | * A Serializer for [LocalTime] as gson cannot directly convert [LocalTime] objects to Json 16 | * a serialization class is added which should be added with the [GsonBuilder] to convert objects 17 | * properly 18 | */ 19 | object LocalTimeGsonSerializer 20 | : JsonSerializer, JsonDeserializer { 21 | 22 | private val formatter = DateTimeFormatter.ISO_TIME 23 | 24 | override fun serialize( 25 | src: LocalTime?, 26 | typeOfSrc: Type?, 27 | context: JsonSerializationContext? 28 | ): JsonElement { 29 | val formattedTime = formatter.format(src) 30 | return JsonPrimitive(formattedTime) 31 | } 32 | 33 | override fun deserialize( 34 | json: JsonElement?, 35 | typeOfT: Type?, 36 | context: JsonDeserializationContext? 37 | ): LocalTime { 38 | val timeASString = json?.asString 39 | return LocalTime.parse(timeASString, formatter) 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/widget/worker/WorkerConstants.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.widget.worker 2 | 3 | object WorkerConstants { 4 | 5 | // WORKER_TAG 6 | const val WIDGET_WORKER = "WIDGET_WORKER" 7 | const val PERIODIC_WORKER = "WIDGET_PERIODIC_WORKER" 8 | 9 | // Widget Data Worker 10 | const val WIDGET_DATA_WORKER_ERROR_KEY = "WIDGET_DATA_ERROR" 11 | const val WIDGET_DATA_WORKER_SUCCESS_KEY = "WIDGET_DATA_SUCCESS" 12 | const val WIDGET_DATA_WORKER_SUCCESS_VALUE = "SUCCESSFULLY_UPDATED_WIDGET" 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/worker/NotificationWorker.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.worker 2 | 3 | import android.content.Context 4 | import androidx.work.Worker 5 | import androidx.work.WorkerParameters 6 | import com.vishal2376.snaptick.util.Constants 7 | import com.vishal2376.snaptick.util.NotificationHelper 8 | 9 | class NotificationWorker(val context: Context, params: WorkerParameters) : 10 | Worker(context, params) { 11 | 12 | private val notificationHelper = NotificationHelper(context) 13 | 14 | override fun doWork(): Result { 15 | //get required data 16 | val taskUUID = inputData.getString(Constants.TASK_UUID) 17 | val taskTitle = inputData.getString(Constants.TASK_TITLE) 18 | val taskTime = inputData.getString(Constants.TASK_TIME) 19 | 20 | if (taskUUID != null && taskTime != null && taskTitle != null) { 21 | notificationHelper.showNotification( 22 | taskUUID.hashCode(), 23 | taskTitle, 24 | taskTime 25 | ) 26 | return Result.success() 27 | } 28 | return Result.failure() 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/vishal2376/snaptick/worker/RepeatTaskWorker.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick.worker 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.hilt.work.HiltWorker 6 | import androidx.work.CoroutineWorker 7 | import androidx.work.Data 8 | import androidx.work.OneTimeWorkRequestBuilder 9 | import androidx.work.WorkManager 10 | import androidx.work.WorkerParameters 11 | import com.vishal2376.snaptick.data.repositories.TaskRepository 12 | import com.vishal2376.snaptick.util.Constants 13 | import dagger.assisted.Assisted 14 | import dagger.assisted.AssistedInject 15 | import java.time.LocalDate 16 | import java.time.LocalTime 17 | import java.util.concurrent.TimeUnit 18 | import kotlin.math.max 19 | 20 | @HiltWorker 21 | class RepeatTaskWorker @AssistedInject constructor( 22 | @Assisted val context: Context, 23 | @Assisted params: WorkerParameters, 24 | private val repository: TaskRepository, 25 | ) : CoroutineWorker(context, params) { 26 | 27 | override suspend fun doWork(): Result { 28 | try { 29 | val dayOfWeek = LocalDate.now().dayOfWeek.value - 1 // because mon-1,sun-7 30 | 31 | val taskList = repository.getLastRepeatedTasks() 32 | 33 | taskList.forEach { task -> 34 | //repeat days of week 35 | val repeatWeekDays = task.getRepeatWeekList() 36 | if (repeatWeekDays.contains(dayOfWeek)) { 37 | if (task.reminder) { 38 | 39 | //cancel old notification request 40 | WorkManager.getInstance(context.applicationContext) 41 | .cancelAllWorkByTag(task.uuid) 42 | 43 | //calculate delay 44 | val startTimeSec = task.startTime.toSecondOfDay() 45 | val currentTimeSec = LocalTime.now().toSecondOfDay() 46 | val delaySec = max(startTimeSec - currentTimeSec, 0) 47 | 48 | if (delaySec > 0 || startTimeSec < 60) { 49 | val data = Data.Builder().putString(Constants.TASK_UUID, task.uuid) 50 | .putString(Constants.TASK_TITLE, task.title) 51 | .putString(Constants.TASK_TIME, task.getFormattedTime()) 52 | .build() 53 | 54 | // new notification request 55 | val workRequest = OneTimeWorkRequestBuilder() 56 | .setInitialDelay(delaySec.toLong(), TimeUnit.SECONDS) 57 | .setInputData(data) 58 | .addTag(task.uuid) 59 | .build() 60 | 61 | WorkManager.getInstance(context.applicationContext).enqueue(workRequest) 62 | } 63 | } 64 | 65 | // disable repeat in old task 66 | val oldTask = task.copy(isRepeated = false) 67 | repository.updateTask(oldTask) 68 | 69 | // insert new task 70 | val newTask = task.copy( 71 | id = 0, 72 | isCompleted = false, 73 | date = LocalDate.now(), 74 | pomodoroTimer = -1, 75 | isRepeated = true 76 | ) 77 | repository.insertTask(newTask) 78 | } 79 | } 80 | return Result.success() 81 | } catch (e: Exception) { 82 | Log.e("@@@", "doWork: Error : ${e.message}") 83 | } 84 | return Result.failure() 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_round_primary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_round_secondary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_task_high.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_task_low.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_task_med.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clock.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_code.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fire.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_instagram.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_linkedin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_moon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_support.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_swipe_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_task_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 27 | 34 | 41 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_theme.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_translate.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_twitter.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_uncheck_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/monochrome_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/font/montserrat.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/font/montserrat.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/font/roboto.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/font/roboto_mono.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_mono_thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/font/roboto_mono_thin.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/widget_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/raw/task_added.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/raw/task_added.mp3 -------------------------------------------------------------------------------- /app/src/main/res/raw/task_completed.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/raw/task_completed.mp3 -------------------------------------------------------------------------------- /app/src/main/res/raw/task_deleted.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/app/src/main/res/raw/task_deleted.mp3 -------------------------------------------------------------------------------- /app/src/main/res/values-da/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dagens Opgaver 4 | Hvad ønsker du at gøre? 5 | Redigér Opgave 6 | Tilføj Opgave 7 | Opdatér Opgave 8 | Vælg 9 | Fuldført 10 | Beklager, der opstod en uventet fejl.\n\nVil du sende en fejlrapport til udvikleren? 11 | Nedbrudsrapport 12 | Send 13 | Annullér 14 | Rapportér Fejl 15 | Forslag 16 | v%1$s 17 | Slet Opgave? 18 | Annullér 19 | Slet 20 | Tilpasset Varighed 21 | Udført 22 | Gentag 23 | Start Tid 24 | Slut Tid 25 | Varighed 26 | Påmindelse 27 | Tilpasset 28 | Fuldførte Opgaver 29 | Analyse 30 | Ledig Tid 31 | Ingen Opgaver 32 | Sortér Opgaver efter 33 | Indstillinger 34 | Denne Uge 35 | Tema 36 | Rapportér Fejl 37 | Bedøm Os 38 | Del App 39 | Prioritet (Lav til Høj) 40 | Prioritet (Høj til Lav) 41 | Start Tid (Nyeste Nederst) 42 | Start Tid (Nyeste Øverst) 43 | Oprettelsestid (Nyeste Nederst) 44 | Oprettelsestid (Nyeste Øverst) 45 | Kalender 46 | Idag 47 | Valgt Dato 48 | Opgaver Tilføjet Succesfuldt 49 | Følg Udvikler 50 | Generelle Indstillinger 51 | Lavet med ♥️ af Vishal Singh 52 | Om 53 | Support 54 | Tema 55 | Sprog 56 | Søvntid 57 | Tidsvælger 58 | Twitter 59 | Github 60 | LinkedIn 61 | Instagram 62 | Vælg Tidsvælger Stil 63 | Ur 64 | Rul 65 | Indstil Søvntid 66 | Min - %1$s \t\t Max - %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-en/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Today Tasks 4 | What would you like to do ? 5 | Edit Task 6 | Add Task 7 | Update Task 8 | Select 9 | Completed 10 | Sorry, An unexpected error occurred.\n\nDo you want to send a crash report to the developer? 11 | Crash Report 12 | Send 13 | Cancel 14 | Report Bug 15 | Suggestions 16 | v%1$s 17 | Delete Task ? 18 | Cancel 19 | Delete 20 | Custom Duration 21 | Done 22 | Repeat 23 | Start Time 24 | End Time 25 | Duration 26 | Reminder 27 | Custom 28 | Completed Tasks 29 | Analysis 30 | Free Time 31 | No Tasks 32 | Sort Tasks by 33 | Settings 34 | This Week 35 | Theme 36 | Report Bugs 37 | Rate Us 38 | Share App 39 | Priority (Low to High) 40 | Priority (High to Low) 41 | Start Time (Latest at Bottom) 42 | Start Time (Latest at Top) 43 | Creation Time (Latest at Bottom) 44 | Creation Time (Latest at Top) 45 | Calender 46 | Today 47 | Selected Date 48 | Tasks Added Successfully 49 | Follow Developer 50 | General Settings 51 | Made with ♥ by Vishal Singh 52 | About 53 | Support 54 | Theme 55 | Language 56 | Sleep Time 57 | Time Picker 58 | Twitter 59 | Github 60 | LinkedIn 61 | Instagram 62 | Choose Time Picker Style 63 | Clock 64 | Scroll 65 | Set Sleep Time 66 | Min - %1$s \t\t Max - %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-fa/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | تسک و وظایف امروز 4 | چه کاری دوست داری انجام بدی؟ 5 | ویزایش تسک 6 | افزودن تسک 7 | یروزرسانی تسک 8 | انتخاب 9 | کامل شده 10 | متاسفیم، اتفاق غیرمنتظره ای رخ داد.\n\nآیا مایلید گزارش خرابی را به برنامه نویس ارسال کنید؟ 11 | گزارش خرابی 12 | ارسال 13 | لغو 14 | گزارش اشکال 15 | پیشنهادات 16 | v%1$s 17 | میخواهید تسک را پاک کنید؟ 18 | لغو 19 | پاک کردن 20 | مدت زمان سفارشی 21 | انجام شده 22 | تکرار 23 | زمان شروع 24 | زمان پایان 25 | مدت زمان 26 | یادآور 27 | سفارشی 28 | تسک انجام شده 29 | تجزیه و تحلیل 30 | وقت آزاد 31 | بدون تسک 32 | مرتب کردن تسک ها بر اساس 33 | تنظیمات 34 | این هفته 35 | پوسته 36 | گزارش مشکل 37 | به ما امتیاز دهید 38 | اشتراک گذاری برنامه 39 | اولویت(کم به زیاد) 40 | اولویت(زیاد به کم) 41 | زمان شروع (آخرین در پایین) 42 | شروع (آخرین در بالا) 43 | زمان ایجاد (آخرین در پایین) 44 | زمان ایجاد (آخرین در بالا) 45 | تقویم 46 | امروز 47 | تاریخ انتخاب شده 48 | تسک با موفقیت اضافه شد 49 | دنبال کردن برنامه نویس 50 | تنظیمات کلی 51 | ساخته شده توسط ♥ by Vishal Singh 52 | درباره 53 | پشتیبانی 54 | پوسته 55 | زبان 56 | تایم خواب 57 | انتخاب کننده زمان 58 | توییتر 59 | گیت هاب 60 | لینکداین 61 | اینستاگرام 62 | سبک انتخاب زمان را انتخاب کنید 63 | ساعت 64 | اسکرول کنید 65 | تنظیم زمان خواب 66 | Min - %1$s \t\t Max - %2$s 1 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 今日のタスク 4 | 何をしたいですか? 5 | タスクを編集 6 | タスクを追加 7 | タスクの更新 8 | 選択 9 | 完了 10 | 申し訳ありませんが、予期しないエラーが発生しました。\n\nクラッシュレポートを開発者に送信しますか? 11 | クラッシュレポート 12 | 送信 13 | キャンセル 14 | バグの報告 15 | 候補 16 | v%1$s 17 | タスクを削除? 18 | キャンセル 19 | 削除 20 | カスタム期間 21 | 完了 22 | 繰り返し 23 | 開始時刻 24 | 終了時刻 25 | 期間 26 | リマインダ 27 | カスタム 28 | 完了したタスク 29 | 分析 30 | 空き時間 31 | タスクなし 32 | タスクを並べ替える 33 | 設定 34 | 今週中 35 | テーマ 36 | バグを報告 37 | 評価してください 38 | アプリを共有 39 | 優先度(低~高) 40 | 優先度 (高から低) 41 | 開始時刻(底部最新) 42 | 開始時刻(上部に最新) 43 | 作成時間 (底部最新) 44 | 作成時間 (最上部に最新) 45 | Calender 46 | 今日 47 | 選択された日付 48 | タスクが正常に追加されました 49 | 開発者をフォロー 50 | 全般設定 51 | Made with ♥️ by Visal Singh 52 | About 53 | サポート 54 | テーマ 55 | 言語 56 | スリープ時間 57 | タイムピッカー 58 | Twitter 59 | Github 60 | LinkedIn 61 | Instagram 62 | 時間選択スタイルを選択 63 | 時計 64 | スクロール 65 | スリープ時間の設定 66 | Min - %1$s \t\t Max - %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-nl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vandaag taken 4 | Wat wilt u doen ? 5 | Taak bewerken 6 | Taak toevoegen 7 | Taak bijwerken 8 | Selecteren 9 | Voltooid 10 | Sorry, er is een onverwachte fout opgetreden.\n\nWilt u een crashrapport naar de ontwikkelaar versturen? 11 | Crash rapport 12 | Verzenden 13 | annuleren 14 | Bug melden 15 | Suggesties 16 | v%1$s 17 | Taak verwijderen? 18 | annuleren 19 | Verwijderen 20 | Aangepaste duur 21 | Voltooid 22 | Herhalen 23 | Start tijd 24 | Eind Tijd 25 | Duur 26 | Herinnering 27 | Aangepaste 28 | Voltooide taken 29 | Analyse 30 | Gratis Tijd 31 | Geen taken 32 | Sorteer Taken op 33 | Instellingen 34 | Deze week 35 | Thema 36 | Fouten melden 37 | Beoordeel ons 38 | App delen 39 | Prioriteit (laag naar hoog) 40 | Prioriteit (hoog naar laag) 41 | Begintijd (het beste aan onderaan) 42 | Begintijd (het beste boven) 43 | Aanmaaktijd (het beste aan onder) 44 | Aanmaaktijd (het beste boven) 45 | Kalender 46 | vandaag 47 | Geselecteerde datum 48 | Taken succesvol toegevoegd 49 | Volg ontwikkelaar 50 | Algemene instellingen 51 | Gemaakt met ♥️ door Vishal Singh 52 | Informatie 53 | Ondersteuning 54 | Thema 55 | Taal 56 | Slaap tijd 57 | Tijd Kiezer 58 | Twitter 59 | Github 60 | LinkedIn 61 | Instagram 62 | Kies Time Picker Stijl 63 | Klok 64 | Scrollen 65 | Slaaptijd instellen 66 | Min. %1$s \t\t Max - %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-no/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | I dag oppgaver 4 | Hva ønsker du å gjøre? 5 | Rediger oppgave 6 | Legg til oppgave 7 | Oppdater oppgave 8 | Velg 9 | Fullført 10 | Sorry, An unexpected error occurred.\n\nDo you want to send a crash report to the developer? 11 | Krasj rapport 12 | Sende 13 | Avbryt 14 | Rapporter feil 15 | Forslag 16 | v%1$s 17 | Slett oppgave? 18 | Avbryt 19 | Slett 20 | Egendefinert varighet 21 | Ferdig 22 | Gjenta 23 | Start tid 24 | Slutt tid 25 | Varighet 26 | Påminnelse 27 | Egendefinert 28 | Fullførte oppgaver 29 | Analyse 30 | Gratis tid 31 | Ingen oppgaver 32 | Sorter oppgaver etter 33 | Innstillinger 34 | Denne uken 35 | Tema 36 | Rapporter feil 37 | Vurder Oss 38 | Del app 39 | Prioritet (Til Høy) 40 | Prioritet (Høy til Lav) 41 | Start tid (nyeste på bunnen) 42 | Start tid (nyeste på toppen) 43 | Opprettelsestid (lengst på bunnen) 44 | Opprettelsestid (øverste på toppen) 45 | Kalender 46 | Idag 47 | Valgt dato 48 | Oppgaver lagt til 49 | Følg utvikleren 50 | Generelle innstillinger 51 | Laget med ♥️ av Vishal Singh 52 | Om 53 | Brukerstøtte 54 | Tema 55 | Språk 56 | Hvilemodus tid 57 | Tid velger 58 | Twitter 59 | Github 60 | LinkedIn 61 | Instagram 62 | Velg tidsvelger stil 63 | Klokke 64 | Rull 65 | Angi hviletid 66 | Min - %1$s \t\t Max - %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Today Tasks 4 | What would you like to do ? 5 | Edit Task 6 | Add Task 7 | Update Task 8 | Select 9 | Completed 10 | Üzgünüz, Beklenmeyen bir hata oluştu.\n\nGeliştiriciye bir kilitlenme raporu göndermek istiyor musunuz? 11 | Çökme Raporu 12 | Gönder 13 | İptal Et 14 | Hata Bildir 15 | Öneriler 16 | v%1$s 17 | Delete Task ? 18 | İptal Et 19 | Delete 20 | Custom Duration 21 | Done 22 | Repeat 23 | Start Time 24 | End Time 25 | Duration 26 | Reminder 27 | Custom 28 | Completed Tasks 29 | Analysis 30 | Free Time 31 | No Tasks 32 | Sort Tasks by 33 | Settings 34 | This Week 35 | Theme 36 | Report Bugs 37 | Bizi değerlendirin 38 | Uygulamayı Paylaş 39 | Priority (Low to High) 40 | Priority (High to Low) 41 | Start Time (Latest at Bottom) 42 | Start Time (Latest at Top) 43 | Creation Time (Latest at Bottom) 44 | Creation Time (Latest at Top) 45 | Calender 46 | Today 47 | Selected Date 48 | Tasks Added Successfully 49 | Follow Developer 50 | General Settings 51 | Made with ♥ by Vishal Singh 52 | About 53 | Support 54 | Theme 55 | Language 56 | Sleep Time 57 | Time Picker 58 | Twitter 59 | Github 60 | LinkedIn 61 | Instagram 62 | Choose Time Picker Style 63 | Clock 64 | Scroll 65 | Set Sleep Time 66 | Min - %1$s \t\t Max - %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Theme 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 今天的任务 4 | 你想做什么? 5 | 编辑任务 6 | 添加任务 7 | 更新任务 8 | 选择 9 | 已完成 10 | 抱歉,软件发生错误.\n\n你想向开发者发送错误日志吗? 11 | 崩溃报告 12 | 发送 13 | 取消 14 | 报告错误 15 | 建议 16 | v%1$s 17 | 删除任务? 18 | 取消 19 | 删除 20 | 自定义持续时间 21 | 完成 22 | 重复 23 | 开始时间 24 | 结束时间 25 | 期限 26 | 提醒 27 | 自定义 28 | 已完成的任务 29 | 分析 30 | 空闲时间 31 | 无任务 32 | 任务排序方式 33 | 设置 34 | 本周 35 | 主题 36 | 报告错误 37 | 给我们评分 38 | 分享应用程序 39 | 优先级(从下到高) 40 | 优先级(从高到低) 41 | 开始时间(最新的底部) 42 | 开始时间(最新在顶部) 43 | 创建时间(最新的底部) 44 | 创建时间(最新在顶部) 45 | Calender 46 | 今日: 47 | 选定日期 48 | 任务添加成功 49 | 关注开发者 50 | 常规设置 51 | 用 ♥ 由 Vishal Singh 52 | 关于 53 | 支持 54 | 主题 55 | 语言 56 | 睡眠时间 57 | 时间选择器 58 | 推特 59 | Github 60 | LinkedIn 61 | Instagram 62 | 选择时间选择器样式 63 | 时钟 64 | 滚动 65 | 设置睡眠时间 66 | 最小- %1$s \t\t 最大- %2$s 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | 12 | 13 | #FF161A30 14 | #FF31304D 15 | 16 | #FF000000 17 | #FF252526 18 | 19 | #FFEAF3FF 20 | #FFD9E9FF 21 | 22 | #FFC7E9A7 23 | 24 | 25 | #FFCCD2D8 26 | #FFFF9737 27 | #FFEB8080 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #161A30 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml-v31/snaptick_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/snaptick_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/java/com/vishal2376/snaptick/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.vishal2376.snaptick 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id("com.android.application") version "8.2.0" apply false 4 | id("org.jetbrains.kotlin.android") version "1.9.23" apply false 5 | id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false 6 | id("com.google.dagger.hilt.android") version "2.49" apply false 7 | } -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /app/src/main/res/values/strings.xml 3 | translation: /app/src/main/res/values-%two_letters_code%/%original_file_name% 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Introducing Snaptick – Your Simple and Free Daily Task Planner! 2 | 3 | Snaptick is a user-friendly app designed to boost your productivity by helping you organize and achieve your daily plans effortlessly. With a clean and easy-to-use interface, this app offers essential features like task creation and editing, task prioritization, a built-in Pomodoro Timer, sorting options, task analysis, and reminder notifications. 4 | 5 | ******************************Features****************************** 6 | * 📝 Create and Edit Tasks 7 | * ⏲️ Pomodoro Timer 8 | * 💾 Offline Backup 9 | * 🔄 Sort Tasks 10 | * ⏰ Analyze Free Time 11 | * 😴 Set Sleep Time 12 | * 🗓️ Manage tasks in Calendar View 13 | * 🎨 Material Dynamic Theme Support 14 | * 🔁 Repeatable Tasks with Notification 15 | * 🎬 Smooth Animations 16 | * 🎨 Modern UI with Cool Themes 17 | * 🌐 Available in 15+ Languages 18 | * 🧩 Create Widgets 19 | 20 | Download Snaptick now and simplify your daily planning. Boost your productivity with a clutter-free, ad-free, and efficient task planner that adapts to your unique style. Start your journey to increased productivity today! 21 | 22 | Credits 23 | App Logo Icon created by "icon_small" from www.flaticon.com 24 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Boost your daily productivity with Snaptick 🚀 -------------------------------------------------------------------------------- /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 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishal2376/snaptick/bf231c0bf4ddd68cbb226470e5af53af4b9a07d2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 26 21:09:12 IST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url = uri("https://jitpack.io") } 14 | } 15 | } 16 | 17 | rootProject.name = "Snaptick" 18 | include(":app") 19 | --------------------------------------------------------------------------------