├── .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 |
4 |
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 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
--------------------------------------------------------------------------------