├── .github
└── workflows
│ └── verify-build.yml
├── .gitignore
├── .idea
├── .gitignore
├── androidTestResultsUserPreferences.xml
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── benchmark-rules.pro
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── baseline-prof.txt
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── pseudoankit
│ │ └── droid
│ │ └── tasky
│ │ ├── TaskyApplication.kt
│ │ ├── di
│ │ └── AppModule.kt
│ │ ├── launcher
│ │ ├── MainActivity.kt
│ │ ├── MainActivityModule.kt
│ │ └── MainActivityViewModel.kt
│ │ ├── navigation
│ │ ├── deeplink
│ │ │ └── DeepLinkProvider.kt
│ │ ├── di
│ │ │ └── NavigationModule.kt
│ │ ├── navgraph
│ │ │ └── NavGraph.kt
│ │ └── navigator
│ │ │ ├── CoreFeatureNavigator.kt
│ │ │ └── feature
│ │ │ ├── AuthNavigatorImpl.kt
│ │ │ └── HomeScreenNavigatorImpl.kt
│ │ └── util
│ │ └── SplashScreenUtil.kt
│ └── res
│ ├── values
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── benchmark
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── pseudoankit
│ └── tasky
│ └── benchmark
│ ├── BaselineProfileGenerator.kt
│ ├── BenchmarkTest.kt
│ └── util
│ ├── AgendaScreenBenchmark.kt
│ ├── AppStartBenchmark.kt
│ ├── AuthBenchmark.kt
│ ├── Constants.kt
│ └── HomeScreenBenchmark.kt
├── build.gradle.kts
├── buildSrc
├── .gitignore
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ ├── Modules.kt
│ ├── Plugins.kt
│ ├── VersionCatalog.kt
│ └── plugin
│ ├── RoomPlugin.kt
│ ├── UnitTestPlugin.kt
│ ├── base
│ ├── CoreLibraryFeaturePlugin.kt
│ └── CorePlugin.kt
│ ├── compose
│ ├── ComposeCorePlugin.kt
│ └── ComposeFeaturePlugin.kt
│ └── util
│ ├── PluginConstants.kt
│ └── PluginExtensions.kt
├── config
└── detekt
│ └── detekt.yml
├── core
├── agenda-manger
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── agendamanger
│ │ │ ├── data
│ │ │ ├── local
│ │ │ │ ├── dao
│ │ │ │ │ └── ReminderDao.kt
│ │ │ │ └── entity
│ │ │ │ │ └── ReminderEntity.kt
│ │ │ └── repository
│ │ │ │ ├── AgendaRepositoryImpl.kt
│ │ │ │ └── ReminderRepositoryImpl.kt
│ │ │ ├── di
│ │ │ └── AgendaManagerModule.kt
│ │ │ ├── domain
│ │ │ ├── mapper
│ │ │ │ └── ReminderMapper.kt
│ │ │ ├── model
│ │ │ │ ├── AgendaItem.kt
│ │ │ │ └── AgendaTypes.kt
│ │ │ ├── repository
│ │ │ │ ├── AgendaRepository.kt
│ │ │ │ └── ReminderRepository.kt
│ │ │ └── usecase
│ │ │ │ └── reminder
│ │ │ │ ├── GetSavedAgendaItemsUseCase.kt
│ │ │ │ ├── SaveReminderUseCase.kt
│ │ │ │ └── TriggerAlarmUseCase.kt
│ │ │ └── util
│ │ │ └── AgendaConstants.kt
│ │ └── test
│ │ └── java
│ │ └── pseudoankit
│ │ └── droid
│ │ └── agendamanger
│ │ ├── data
│ │ └── repository
│ │ │ ├── AgendaRepositoryImplTest.kt
│ │ │ └── ReminderRepositoryImplTest.kt
│ │ └── util
│ │ └── TestUtil.kt
├── alarm-manager
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── pseudoankit
│ │ └── droid
│ │ └── alarm_scheduler
│ │ ├── data
│ │ ├── AlarmReceiver.kt
│ │ └── AndroidAlarmScheduler.kt
│ │ ├── di
│ │ └── AlarmManagerModule.kt
│ │ └── domain
│ │ ├── AlarmScheduler.kt
│ │ └── model
│ │ └── Alarm.kt
├── app-shortcuts-n-widgets
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── pseudoankit
│ │ │ └── droid
│ │ │ ├── WidgetsNShortcutsManager.kt
│ │ │ ├── app_shortcuts
│ │ │ └── TaskyShortCutManager.kt
│ │ │ ├── app_widgets
│ │ │ ├── agenda_items
│ │ │ │ ├── AgendaItemAppWidgetReceiver.kt
│ │ │ │ └── AgendaItemsAppWidget.kt
│ │ │ └── util
│ │ │ │ └── WidgetDeeplinkProvider.kt
│ │ │ └── di
│ │ │ └── WidgetsNShortcutsModule.kt
│ │ └── res
│ │ └── xml
│ │ └── tasky_widget_info.xml
├── core-ui
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── coreui
│ │ │ ├── deeplink
│ │ │ └── DeepLinkReciever.kt
│ │ │ ├── destination
│ │ │ └── TaskyDestinationStyle.kt
│ │ │ ├── koin
│ │ │ └── ComposeKoinModule.kt
│ │ │ ├── model
│ │ │ └── TextFieldUiConfig.kt
│ │ │ └── util
│ │ │ └── extension
│ │ │ ├── ContextExtension.kt
│ │ │ ├── ModifierExtension.kt
│ │ │ ├── NavControllerExtension.kt
│ │ │ ├── OrbitMviExtension.kt
│ │ │ ├── StateExtension.kt
│ │ │ └── TextResourceExtension.kt
│ │ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── ic_splash.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ └── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
├── core
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── core
│ │ │ ├── coroutine
│ │ │ └── CoroutineUtil.kt
│ │ │ ├── deeplink
│ │ │ ├── DeepLinkUtil.kt
│ │ │ └── TaskyDeeplink.kt
│ │ │ ├── di
│ │ │ └── CoreModule.kt
│ │ │ ├── dispatcher
│ │ │ ├── DispatcherProvider.kt
│ │ │ └── DispatcherProviderImpl.kt
│ │ │ ├── koin
│ │ │ └── BaseKoinModule.kt
│ │ │ ├── logger
│ │ │ └── TaskyLogger.kt
│ │ │ ├── model
│ │ │ ├── TaskyDate.kt
│ │ │ └── TaskyTime.kt
│ │ │ ├── testtag
│ │ │ ├── AgendaTestTag.kt
│ │ │ ├── AuthTestTag.kt
│ │ │ └── HomeTestTag.kt
│ │ │ ├── util
│ │ │ ├── JsonSerializer.kt
│ │ │ ├── TaskyResult.kt
│ │ │ ├── TextResource.kt
│ │ │ ├── datetime
│ │ │ │ ├── DateUtils.kt
│ │ │ │ └── TimeUtils.kt
│ │ │ ├── extension
│ │ │ │ ├── CollectionExtension.kt
│ │ │ │ ├── DateTimeExtension.kt
│ │ │ │ ├── IntentExtension.kt
│ │ │ │ ├── LazyInitializerExtension.kt
│ │ │ │ ├── PrimitiveExtension.kt
│ │ │ │ └── SafeCall.kt
│ │ │ └── validator
│ │ │ │ └── Validator.kt
│ │ │ └── widget
│ │ │ └── UpdateAppWidgetFlow.kt
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── database-manager
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── pseudoankit
│ │ └── droid
│ │ └── dbmanager
│ │ ├── TaskyDataBase.kt
│ │ ├── di
│ │ └── DataBaseModule.kt
│ │ └── typeconvertor
│ │ └── DateTimeTypeConvertor.kt
├── design-system
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── debug
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── unify
│ │ │ └── UnifyActivity.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── unify
│ │ │ ├── component
│ │ │ ├── button
│ │ │ │ ├── UnifyButton.kt
│ │ │ │ └── UnifyButtonMapper.kt
│ │ │ ├── card
│ │ │ │ └── UnifyCard.kt
│ │ │ ├── dialog
│ │ │ │ ├── UnifyDialog.kt
│ │ │ │ ├── UnifyDialogState.kt
│ │ │ │ ├── datepicker
│ │ │ │ │ ├── UnifyDatePicker.kt
│ │ │ │ │ └── UnifyDatePickerToken.kt
│ │ │ │ └── timepicker
│ │ │ │ │ ├── UnifyTimePicker.kt
│ │ │ │ │ └── UnifyTimePickerToken.kt
│ │ │ ├── divider
│ │ │ │ └── UnifyDivider.kt
│ │ │ ├── fab
│ │ │ │ └── UnifyFloatingButton.kt
│ │ │ ├── icon
│ │ │ │ ├── UnifyIcon.kt
│ │ │ │ ├── UnifyIconInternal.kt
│ │ │ │ └── UnifyIcons.kt
│ │ │ ├── list
│ │ │ │ ├── UnifyListComponents.kt
│ │ │ │ └── UnifyListItem.kt
│ │ │ ├── pill
│ │ │ │ └── UnifyPill.kt
│ │ │ ├── progressbar
│ │ │ │ └── UnifyProgressIndicator.kt
│ │ │ ├── swipeablecard
│ │ │ │ └── SwipeableCard.kt
│ │ │ ├── switch
│ │ │ │ ├── UnifySwitch.kt
│ │ │ │ └── UnifySwitchTokens.kt
│ │ │ ├── textfield
│ │ │ │ ├── UnifyTextField.kt
│ │ │ │ ├── UnifyTextFieldDefaults.kt
│ │ │ │ └── internal
│ │ │ │ │ ├── UnifyTextFieldCommon.kt
│ │ │ │ │ ├── UnifyTextFieldInternal.kt
│ │ │ │ │ └── UnifyTextFieldTokens.kt
│ │ │ ├── textview
│ │ │ │ ├── UnifyTextType.kt
│ │ │ │ └── UnifyTextView.kt
│ │ │ ├── topbar
│ │ │ │ ├── UnifySmallTopBar.kt
│ │ │ │ └── UnifyTopBar.kt
│ │ │ └── viewpager
│ │ │ │ ├── HorizontalTabsInternal.kt
│ │ │ │ └── HorizontalViewPager.kt
│ │ │ ├── screen
│ │ │ ├── UnifyScreen.kt
│ │ │ └── UnifyScreenConfig.kt
│ │ │ ├── token
│ │ │ ├── UnifyColors.kt
│ │ │ ├── UnifyDimens.kt
│ │ │ ├── UnifyTheme.kt
│ │ │ └── UnifyTokens.kt
│ │ │ └── utils
│ │ │ ├── DarkThemeRipple.kt
│ │ │ ├── Logs.kt
│ │ │ ├── ModifierExtension.kt
│ │ │ ├── Utils.kt
│ │ │ └── internal
│ │ │ └── Utils.kt
│ │ └── res
│ │ └── drawable
│ │ ├── ic_add.xml
│ │ ├── ic_calendar.xml
│ │ ├── ic_notification.xml
│ │ ├── ic_task.xml
│ │ └── img_placeholder.xml
├── notification-manager
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── notification_manager
│ │ │ ├── TaskyNotifier.kt
│ │ │ ├── TaskyNotifierConfig.kt
│ │ │ ├── di
│ │ │ └── NotifierModule.kt
│ │ │ └── util
│ │ │ └── TaskyNotifierUtils.kt
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── permission-manager
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── permission_manager
│ │ ├── PermissionUtils.kt
│ │ └── TaskyPermissionStatus.kt
├── preferences-manager
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── pseudoankit
│ │ └── droid
│ │ ├── di
│ │ └── PreferencesModule.kt
│ │ └── preferencesmanager
│ │ ├── BasePreference.kt
│ │ ├── Keys.kt
│ │ └── PreferenceRepository.kt
└── test-helper
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── pseudoankit
│ └── test_helper
│ ├── CoroutineDispatcherRule.kt
│ ├── TestCoroutineDispatcherProvider.kt
│ └── TestUtils.kt
├── feature
├── agenda
│ ├── event
│ │ ├── .gitignore
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── AndroidManifest.xml
│ ├── reminder
│ │ ├── .gitignore
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── tasky
│ │ │ └── reminder
│ │ │ ├── di
│ │ │ └── ReminderModule.kt
│ │ │ ├── navigator
│ │ │ ├── ActionNavTypeSerializer.kt
│ │ │ ├── ReminderDeepLinkProvider.kt
│ │ │ └── ReminderNavigator.kt
│ │ │ └── presentation
│ │ │ ├── ReminderUiState.kt
│ │ │ ├── ReminderViewModel.kt
│ │ │ ├── mapper
│ │ │ ├── ReminderMapper.kt
│ │ │ └── RepeatIntervalUiMapper.kt
│ │ │ └── ui
│ │ │ ├── ReminderScreen.kt
│ │ │ └── ReminderScreenComponents.kt
│ └── task
│ │ ├── .gitignore
│ │ ├── build.gradle.kts
│ │ └── src
│ │ └── main
│ │ └── AndroidManifest.xml
├── authentication
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── pseudoankit
│ │ │ └── droid
│ │ │ └── authentication
│ │ │ ├── di
│ │ │ ├── LoginModule.kt
│ │ │ └── RegistrationModule.kt
│ │ │ ├── domain
│ │ │ ├── LoginUserUseCase.kt
│ │ │ └── RegisterUserUseCase.kt
│ │ │ ├── navigator
│ │ │ └── AuthNavigator.kt
│ │ │ └── presentation
│ │ │ ├── login
│ │ │ ├── LoginUiState.kt
│ │ │ ├── LoginViewModel.kt
│ │ │ └── ui
│ │ │ │ ├── LoginScreen.kt
│ │ │ │ └── LoginScreenComponents.kt
│ │ │ └── registration
│ │ │ ├── RegistrationScreen.kt
│ │ │ ├── RegistrationUiState.kt
│ │ │ ├── RegistrationViewModel.kt
│ │ │ └── ui
│ │ │ └── RegistrationScreenComponents.kt
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── developer-tools
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── AndroidManifest.xml
├── home
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── pseudoankit
│ │ └── droid
│ │ └── tasky
│ │ └── home
│ │ ├── di
│ │ ├── AgendaItemScreenModule.kt
│ │ └── HomeScreenModule.kt
│ │ ├── domain
│ │ └── usecase
│ │ │ ├── DeleteAgendaUseCase.kt
│ │ │ └── ToggleAgendaItemCompletionUseCase.kt
│ │ ├── navigator
│ │ ├── HomeDeepLinkProvider.kt
│ │ └── HomeScreenNavigator.kt
│ │ └── presentation
│ │ ├── home
│ │ ├── HomeUiState.kt
│ │ ├── HomeViewModel.kt
│ │ └── ui
│ │ │ ├── AgendaReminderCard.kt
│ │ │ ├── HomeScreen.kt
│ │ │ ├── HomeScreenComponents.kt
│ │ │ ├── SavedAgendaItem.kt
│ │ │ └── ShowBannerAndRequestIfNotificationPermissionDenied.kt
│ │ ├── mapper
│ │ ├── AgendaItemsUiMapper.kt
│ │ └── AgendaTypesUiMapper.kt
│ │ └── taskyitems
│ │ ├── AgendaItemsScreen.kt
│ │ ├── AgendaItemsUiState.kt
│ │ └── AgendaItemsViewModel.kt
└── profile
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── pseudoankit
│ └── droid
│ └── profile
│ └── presentation
│ └── ui
│ └── ProfileScreen.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── preview
├── add-reminder.png
├── home.png
├── login.png
├── shortcut.png
└── widget.png
├── renovate.json
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tasky
2 | App to set events & reminder, repo created to apply new concepts in practice
3 |
4 | Login | Home | App Widget | Add Reminder | Shortcuts
5 | | :---------------: | :---------------: | :---------------: | :---------------: | :---------------: |
6 |
|
|
|
|
7 |
8 |
9 | [Demo](https://drive.google.com/file/d/1FpIbR176fost0GjCY3byaZ6Bqba6ioTY/view?usp=sharing)
10 |
11 |
12 | 🎯 Key Features:
13 |
14 | - Architecture :- MVI [uses orbit mvi](https://orbit-mvi.org/) + Clean Architecture
15 | - Single Activity Arch
16 | - Multi-Module Setup:- demonstrates how to structure an Android project with multiple modules for better code organization and scalability.
17 | - Dependency Injection:- [Koin](https://github.com/InsertKoinIO/koin)
18 | - Jetpack Compose UI
19 | - Navigation in Multi-Module project [uses compose destinations](https://github.com/raamcosta/compose-destinations)
20 | - CI/CD Setup
21 | - generate apk on push to master
22 | - static code analysis with sonarcloud
23 | - lint check
24 | - BuildSrc Setup: Dependency management is centralized using BuildSrc, simplifying dependency declarations and version management.
25 | - Adding widgets and app shortcut
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | ../.idea
--------------------------------------------------------------------------------
/app/benchmark-rules.pro:
--------------------------------------------------------------------------------
1 | -dontobfuscate
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
21 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/TaskyApplication.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky
2 |
3 | import android.app.Application
4 | import org.koin.android.ext.koin.androidContext
5 | import org.koin.android.ext.koin.androidLogger
6 | import org.koin.core.context.loadKoinModules
7 | import org.koin.core.context.startKoin
8 | import pseudoankit.droid.WidgetsNShortcutsManager
9 | import pseudoankit.droid.tasky.di.AppModule
10 |
11 | class TaskyApplication : Application() {
12 |
13 | override fun onCreate() {
14 | super.onCreate()
15 | loadKoinModules()
16 | }
17 |
18 | private fun loadKoinModules() {
19 | startKoin {
20 | androidLogger()
21 | androidContext(this@TaskyApplication)
22 | }
23 | loadKoinModules(AppModule)
24 | WidgetsNShortcutsManager.initialize(context = this)
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.di
2 |
3 | import pseudoankit.droid.agendamanger.di.AgendaManagerModule
4 | import pseudoankit.droid.alarm_scheduler.di.AlarmManagerModule
5 | import pseudoankit.droid.core.di.CoreModule
6 | import pseudoankit.droid.dbmanager.di.DataBaseModule
7 | import pseudoankit.droid.di.PreferencesModule
8 | import pseudoankit.droid.di.WidgetsNShortcutsModule
9 | import pseudoankit.droid.notification_manager.di.NotifierModule
10 | import pseudoankit.droid.tasky.navigation.di.NavigationModule
11 |
12 | internal val AppModule = listOf(
13 | DataBaseModule(),
14 | AgendaManagerModule(), // todo remove
15 | AlarmManagerModule(),
16 | NavigationModule(),
17 | CoreModule(),
18 | NotifierModule(),
19 | PreferencesModule(),
20 | WidgetsNShortcutsModule()
21 | )
22 |
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/launcher/MainActivityModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.launcher
2 |
3 | import org.koin.androidx.viewmodel.dsl.viewModel
4 | import org.koin.core.module.Module
5 | import org.koin.dsl.module
6 | import pseudoankit.droid.core.koin.BaseKoinModule
7 |
8 | object MainActivityModule : BaseKoinModule() {
9 |
10 | override val modules: Module
11 | get() = module {
12 | viewModel { MainActivityViewModel(get()) }
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/navigation/deeplink/DeepLinkProvider.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.navigation.deeplink
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
4 | import pseudoankit.droid.app_widgets.util.WidgetDeeplinkProvider
5 | import pseudoankit.droid.core.deeplink.TaskyDeeplink
6 | import pseudoankit.droid.tasky.home.navigator.HomeDeepLinkProvider
7 | import pseudoankit.droid.tasky.reminder.navigator.ActionNavTypeSerializer
8 | import pseudoankit.droid.tasky.reminder.navigator.ReminderDeepLinkProvider
9 |
10 | /**
11 | * Deeplink provider, This class will contains method to provide different screen deeplink
12 | * Just pass the required data and it will convert it the proper deeplink
13 | */
14 | internal class DeepLinkProvider : ReminderDeepLinkProvider, HomeDeepLinkProvider,
15 | WidgetDeeplinkProvider {
16 |
17 | override fun agendaDetailRoute(action: AgendaTypes): String = when (action) {
18 | is AgendaTypes.Event -> TODO()
19 | is AgendaTypes.Reminder -> reminderScreenRoute(action.action)
20 | is AgendaTypes.Task -> TODO()
21 | }
22 |
23 | override fun reminderScreenRoute(action: AgendaTypes.Action): String {
24 | return TaskyDeeplink.reminder.replace(
25 | TaskyDeeplink.Path.Reminder.action,
26 | ActionNavTypeSerializer.toRouteString(action)
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/navigation/di/NavigationModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.navigation.di
2 |
3 | import org.koin.dsl.module
4 | import pseudoankit.droid.app_widgets.util.WidgetDeeplinkProvider
5 | import pseudoankit.droid.tasky.home.navigator.HomeDeepLinkProvider
6 | import pseudoankit.droid.tasky.navigation.deeplink.DeepLinkProvider
7 | import pseudoankit.droid.tasky.reminder.navigator.ReminderDeepLinkProvider
8 |
9 | object NavigationModule {
10 |
11 | operator fun invoke() = module {
12 | val deepLinkProvider = DeepLinkProvider()
13 | factory { deepLinkProvider }
14 | factory { deepLinkProvider }
15 | factory { deepLinkProvider }
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/navigation/navgraph/NavGraph.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.navigation.navgraph
2 |
3 | import com.ramcosta.composedestinations.spec.DestinationSpec
4 | import com.ramcosta.composedestinations.spec.NavGraphSpec
5 | import com.ramcosta.composedestinations.spec.Route
6 | import pseudoankit.droid.authentication.presentation.authenticationDestinations
7 | import pseudoankit.droid.authentication.presentation.destinations.LoginScreenDestination
8 | import pseudoankit.droid.tasky.home.presentation.destinations.HomeScreenDestination
9 | import pseudoankit.droid.tasky.home.presentation.homeDestinations
10 | import pseudoankit.droid.tasky.reminder.presentation.ui.destinations.ReminderScreenDestination
11 |
12 | object NavGraph {
13 |
14 | private val auth = object : NavGraphSpec {
15 | override val destinationsByRoute: Map> =
16 | authenticationDestinations
17 | .associateBy { it.route }
18 | override val route: String = "auth"
19 | override val startRoute: Route = LoginScreenDestination
20 | }
21 |
22 | private val main = object : NavGraphSpec {
23 | override val destinationsByRoute: Map> =
24 | homeDestinations
25 | .plus(ReminderScreenDestination)
26 | .associateBy { it.route }
27 | override val route: String = "main"
28 | override val startRoute: Route = HomeScreenDestination
29 | }
30 |
31 | fun root(isUserLoggedIn: Boolean) = object : NavGraphSpec {
32 | override val destinationsByRoute: Map> = emptyMap()
33 | override val route: String = "root"
34 | override val startRoute: Route = if (isUserLoggedIn) main else auth
35 | override val nestedNavGraphs: List = listOf(
36 | auth, main
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/navigation/navigator/CoreFeatureNavigator.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.navigation.navigator
2 |
3 | import android.content.Context
4 | import androidx.navigation.NavController
5 | import com.ramcosta.composedestinations.navigation.navigate
6 | import pseudoankit.droid.authentication.navigator.AuthNavigator
7 | import pseudoankit.droid.coreui.util.extension.clearStack
8 | import pseudoankit.droid.coreui.util.extension.finish
9 | import pseudoankit.droid.tasky.home.navigator.HomeScreenNavigator
10 | import pseudoankit.droid.tasky.home.presentation.destinations.HomeScreenDestination
11 | import pseudoankit.droid.tasky.navigation.navigator.feature.AuthNavigatorImpl
12 | import pseudoankit.droid.tasky.navigation.navigator.feature.HomeScreenNavigatorImpl
13 | import pseudoankit.droid.tasky.reminder.navigator.ReminderNavigator
14 |
15 | class CoreFeatureNavigator(
16 | private val navController: NavController,
17 | private val context: Context
18 | ) : AuthNavigator by AuthNavigatorImpl(navController, context),
19 | HomeScreenNavigator by HomeScreenNavigatorImpl(navController, context),
20 | ReminderNavigator {
21 |
22 | override fun navigateToHomeScreen() {
23 | navController.navigate(HomeScreenDestination) {
24 | navController.clearStack(this)
25 | }
26 | }
27 |
28 | override fun navigateUp() {
29 | if (navController.popBackStack().not()) {
30 | context.finish()
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/navigation/navigator/feature/AuthNavigatorImpl.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.navigation.navigator.feature
2 |
3 | import android.content.Context
4 | import androidx.navigation.NavController
5 | import com.ramcosta.composedestinations.navigation.navigate
6 | import pseudoankit.droid.authentication.navigator.AuthNavigator
7 | import pseudoankit.droid.authentication.presentation.destinations.RegistrationScreenDestination
8 |
9 | internal class AuthNavigatorImpl(
10 | private val navController: NavController,
11 | private val context: Context
12 | ) : AuthNavigator {
13 |
14 | override fun navigateUp() {
15 | // overridden in CoreNavigatorImpl
16 | }
17 |
18 | override fun navigateToHomeScreen() {
19 | // overridden in CoreNavigatorImpl
20 | }
21 |
22 | override fun navigateToRegistrationScreen() {
23 | navController.navigate(RegistrationScreenDestination)
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/navigation/navigator/feature/HomeScreenNavigatorImpl.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.navigation.navigator.feature
2 |
3 | import android.content.Context
4 | import androidx.navigation.NavController
5 | import com.ramcosta.composedestinations.navigation.navigate
6 | import kotlinx.coroutines.runBlocking
7 | import org.koin.java.KoinJavaComponent.inject
8 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
9 | import pseudoankit.droid.core.deeplink.TaskyDeeplink
10 | import pseudoankit.droid.coreui.deeplink.navigateViaDeepLink
11 | import pseudoankit.droid.coreui.util.extension.toastNotImplemented
12 | import pseudoankit.droid.preferencesmanager.PreferenceRepository
13 | import pseudoankit.droid.tasky.home.navigator.HomeScreenNavigator
14 | import pseudoankit.droid.tasky.reminder.presentation.ui.destinations.ReminderScreenDestination
15 |
16 | internal class HomeScreenNavigatorImpl(
17 | private val navController: NavController,
18 | private val context: Context
19 | ) : HomeScreenNavigator {
20 |
21 | private val prefs: PreferenceRepository by inject(PreferenceRepository::class.java)
22 |
23 | override fun navigateToProfileScreen() {
24 | runBlocking { prefs.setIsLoggedIn(false) }
25 | }
26 |
27 | override fun navigateToAgendaItemsSelectorScreen() {
28 | navController.navigateViaDeepLink(TaskyDeeplink.agendaSelection)
29 | }
30 |
31 | override fun navigateToAgendaScreen(agendaTypes: AgendaTypes) {
32 | when (agendaTypes) {
33 | is AgendaTypes.Reminder -> navController.navigate(
34 | ReminderScreenDestination(agendaTypes.action)
35 | )
36 | is AgendaTypes.Event -> context.toastNotImplemented()
37 | is AgendaTypes.Task -> context.toastNotImplemented()
38 | }
39 | }
40 |
41 | override fun navigateUp() {
42 | // overridden in CoreNavigatorImpl
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/pseudoankit/droid/tasky/util/SplashScreenUtil.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.util
2 |
3 | import androidx.core.splashscreen.SplashScreen
4 |
5 | fun SplashScreen.show() {
6 | setKeepOnScreenCondition { true }
7 | }
8 |
9 | fun SplashScreen.hide() {
10 | setKeepOnScreenCondition { false }
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Tasky
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/benchmark/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/benchmark/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.android.build.api.dsl.ManagedVirtualDevice
2 |
3 | plugins {
4 | id("com.android.test")
5 | id(Plugins.Core)
6 | }
7 |
8 | android {
9 | namespace = "com.pseudoankit.tasky.benchmark"
10 |
11 | defaultConfig {
12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 |
15 | buildTypes {
16 | // This benchmark buildType is used for benchmarking, and should function like your
17 | // release build (for example, with minification on). It"s signed with a debug key
18 | // for easy local/CI testing.
19 | create("benchmark") {
20 | isDebuggable = true
21 | signingConfig = getByName("debug").signingConfig
22 | matchingFallbacks += listOf("release")
23 | proguardFiles("benchmark-rules.pro")
24 | }
25 | }
26 |
27 | testOptions {
28 | managedDevices {
29 | devices {
30 | create("pixelapi31", ManagedVirtualDevice::class) {
31 | device = "Pixel"
32 | apiLevel = 31
33 | systemImageSource = "aosp"
34 | }
35 | }
36 | }
37 | }
38 |
39 | targetProjectPath = ":app"
40 | experimentalProperties["android.experimental.self-instrumenting"] = true
41 | }
42 |
43 | dependencies {
44 | implementation(projects.core.core)
45 | implementation(libs.androidx.benchmark)
46 | implementation(libs.bundles.uiTest)
47 | }
48 |
49 | androidComponents {
50 | beforeVariants(selector().all()) {
51 | it.enabled = it.buildType == "benchmark"
52 | }
53 | }
--------------------------------------------------------------------------------
/benchmark/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/pseudoankit/tasky/benchmark/BaselineProfileGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.tasky.benchmark
2 |
3 | import androidx.benchmark.macro.junit4.BaselineProfileRule
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.pseudoankit.tasky.benchmark.util.PackageName
6 | import com.pseudoankit.tasky.benchmark.util.performHomeScreenOperations
7 | import org.junit.Rule
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 |
11 | /*
12 | * to generate run
13 | * ./gradlew :benchmark:pixelapi31BenchmarkAndroidTest -P android.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
14 | */
15 |
16 | @RunWith(AndroidJUnit4::class)
17 | class BaselineProfileGenerator {
18 |
19 | @get:Rule
20 | val rule = BaselineProfileRule()
21 |
22 | @Test
23 | fun homeScreenTest() = rule.collectBaselineProfile(
24 | packageName = PackageName
25 | ) {
26 | performHomeScreenOperations()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/pseudoankit/tasky/benchmark/BenchmarkTest.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.tasky.benchmark
2 |
3 | import androidx.benchmark.macro.CompilationMode
4 | import androidx.benchmark.macro.FrameTimingMetric
5 | import androidx.benchmark.macro.StartupMode
6 | import androidx.benchmark.macro.StartupTimingMetric
7 | import androidx.benchmark.macro.junit4.MacrobenchmarkRule
8 | import androidx.test.ext.junit.runners.AndroidJUnit4
9 | import com.pseudoankit.tasky.benchmark.util.PackageName
10 | import com.pseudoankit.tasky.benchmark.util.openApplication
11 | import com.pseudoankit.tasky.benchmark.util.performHomeScreenOperations
12 | import org.junit.Rule
13 | import org.junit.Test
14 | import org.junit.runner.RunWith
15 |
16 | @RunWith(AndroidJUnit4::class)
17 | class BenchmarkTest {
18 | @get:Rule
19 | val benchmarkRule = MacrobenchmarkRule()
20 |
21 | @Test
22 | fun startUpCompilationPartial() = startup(CompilationMode.Partial())
23 |
24 | @Test
25 | fun startUpCompilationNone() = startup(CompilationMode.None())
26 |
27 | @Test
28 | fun homeScreenTestCompilationPartial() = homeScreenTest(CompilationMode.Partial())
29 |
30 | @Test
31 | fun homeScreenTestCompilationNone() = homeScreenTest(CompilationMode.None())
32 |
33 | private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
34 | packageName = PackageName,
35 | metrics = listOf(StartupTimingMetric()),
36 | iterations = 5,
37 | startupMode = StartupMode.COLD,
38 | compilationMode = compilationMode
39 | ) {
40 | openApplication()
41 | }
42 |
43 | private fun homeScreenTest(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
44 | packageName = PackageName,
45 | metrics = listOf(FrameTimingMetric()),
46 | iterations = 5,
47 | startupMode = StartupMode.COLD,
48 | compilationMode = compilationMode
49 | ) {
50 | performHomeScreenOperations()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/pseudoankit/tasky/benchmark/util/AgendaScreenBenchmark.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.tasky.benchmark.util
2 |
3 | import androidx.benchmark.macro.MacrobenchmarkScope
4 | import androidx.test.uiautomator.By
5 | import pseudoankit.droid.core.testtag.AgendaTestTag
6 | import pseudoankit.droid.core.testtag.ReminderTestTag
7 |
8 | fun MacrobenchmarkScope.addReminder(
9 | text: String = "Remind me about ${Math.random()}"
10 | ) {
11 | val edtReminder = device.findObject(By.res(ReminderTestTag.edtRemindMe))
12 | val btnSave = device.findObject(By.res(AgendaTestTag.save))
13 |
14 | edtReminder.text = text
15 |
16 | btnSave.click()
17 | }
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/pseudoankit/tasky/benchmark/util/AppStartBenchmark.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.tasky.benchmark.util
2 |
3 | import androidx.benchmark.macro.MacrobenchmarkScope
4 |
5 | fun MacrobenchmarkScope.openApplication() {
6 | pressHome()
7 | startActivityAndWait()
8 | }
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/pseudoankit/tasky/benchmark/util/AuthBenchmark.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.tasky.benchmark.util
2 |
3 | import androidx.benchmark.macro.MacrobenchmarkScope
4 | import androidx.test.uiautomator.By
5 | import pseudoankit.droid.core.testtag.AuthTestTag
6 |
7 | fun MacrobenchmarkScope.performLogin() {
8 | val email = device.findObject(By.res(AuthTestTag.email))
9 | val password = device.findObject(By.res(AuthTestTag.password))
10 | val loginBtn = device.findObject(By.res(AuthTestTag.loginBtn))
11 |
12 | email.text = "lostankit7@gmail.com"
13 | password.text = "qwerty@123"
14 | loginBtn.click()
15 | }
--------------------------------------------------------------------------------
/benchmark/src/main/java/com/pseudoankit/tasky/benchmark/util/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.tasky.benchmark.util
2 |
3 | const val PackageName = "pseudoankit.droid.tasky"
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | maven { url = uri("https://plugins.gradle.org/m2/") }
7 | }
8 | dependencies {
9 | classpath(libs.android.gradle.plugin)
10 | classpath(libs.kotlin.gradle.plugin)
11 | classpath(libs.kotlin.serialization.plugin)
12 | classpath(libs.sonarQube.gradle.plugin)
13 | classpath(libs.detekt.gradle.plugin)
14 | classpath(libs.ksp.gradle.plugin)
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | mavenCentral()
22 | maven { url = uri("https://plugins.gradle.org/m2/") }
23 | maven { url = uri("https://jitpack.io") }
24 | }
25 | }
26 |
27 | tasks.register("clean", Delete::class) {
28 | delete(rootProject.buildDir)
29 | }
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | repositories {
6 | gradlePluginPortal()
7 | google()
8 | }
9 |
10 | dependencies {
11 | implementation(libs.android.gradle.plugin)
12 | implementation(libs.kotlin.gradle.plugin)
13 |
14 | // Make version catalog available in precompiled scripts
15 | // https://github.com/gradle/gradle/issues/15383#issuecomment-1567461389
16 | implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
17 | }
18 |
19 | gradlePlugin {
20 | plugins {
21 | val plugins = listOf(
22 | "core-plugin" to "plugin.base.CorePlugin",
23 | "core-library-feature-plugin" to "plugin.base.CoreLibraryFeaturePlugin",
24 | "compose-core-plugin" to "plugin.compose.ComposeCorePlugin",
25 | "compose-feature-plugin" to "plugin.compose.ComposeFeaturePlugin",
26 | "room-db-plugin" to "plugin.RoomPlugin",
27 | "ut-plugin" to "plugin.UnitTestPlugin"
28 | )
29 |
30 | plugins.forEach {
31 | register(it.first) {
32 | id = it.first
33 | implementationClass = it.second
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/buildSrc/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | dependencyResolutionManagement {
9 | repositories {
10 | google()
11 | gradlePluginPortal()
12 | }
13 |
14 | versionCatalogs {
15 | create("libs") {
16 | from(files("../gradle/libs.versions.toml"))
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/buildSrc/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Modules.kt:
--------------------------------------------------------------------------------
1 | object Modules {
2 | object Core {
3 | const val Core = ":core:core"
4 | const val CoreUi = ":core:core-ui"
5 | const val DesignSystem = ":core:design-system"
6 | const val TestHelper = ":core:test-helper"
7 | }
8 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Plugins.kt:
--------------------------------------------------------------------------------
1 | object Plugins {
2 |
3 | /**
4 | * Plugin containing all common code for any gradle
5 | * It includes defaultConfigs, compileOptions, kotlinOptions and koin deps
6 | * Serialization, Parcelable,
7 | * ### Every module should atleast include this plugin
8 | */
9 | const val Core = "core-plugin"
10 |
11 | /**
12 | * Plugin containing [com.android.library] plugin, core module along with [Plugins.Core] features internally
13 | * ###Applicable in library modules only
14 | */
15 | const val CoreFeatureLib = "core-library-feature-plugin"
16 |
17 | /**
18 | * Plugin containing setup for enabling compose at module where applied along with compose core deps
19 | */
20 | const val ComposeCore = "compose-core-plugin"
21 |
22 | /**
23 | * Plugin containing setup for adding compose core [Plugins.ComposeCore] deps with navigation library setup
24 | * It also includes coreUi, koin deps, design-system orbit MVI deps
25 | * Mainly this is intented to use at feature module
26 | * ###Applicable only for library level module
27 | */
28 | const val ComposeFeatureLib = "compose-feature-plugin"
29 |
30 | /**
31 | * Plugin containing setup to enable room db
32 | */
33 | const val RoomDatabase = "room-db-plugin"
34 |
35 | /** Plugin containing setup to add unit test in a module */
36 | const val UnitTestPlugin = "ut-plugin"
37 |
38 | const val AndroidLibrary = "com.android.library"
39 | const val Ksp = "com.google.devtools.ksp"
40 | const val Detekt = "io.gitlab.arturbosch.detekt"
41 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/VersionCatalog.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("Filename")
2 |
3 | import org.gradle.accessors.dm.LibrariesForLibs
4 | import org.gradle.api.Project
5 | import org.gradle.api.artifacts.VersionCatalog
6 | import org.gradle.api.artifacts.VersionCatalogsExtension
7 | import org.gradle.kotlin.dsl.getByType
8 | import org.gradle.kotlin.dsl.the
9 |
10 | //val org.gradle.api.Project.`projects`: org.gradle.accessors.dm.RootProjectAccessor get() =
11 | // (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("projects") as org.gradle.accessors.dm.RootProjectAccessor
12 |
13 | // Make version catalog available in precompiled scripts
14 | // https://github.com/gradle/gradle/issues/15383#issuecomment-1567461389
15 | val Project.libs: LibrariesForLibs
16 | get() = the()
17 |
18 | internal val Project.catalog: VersionCatalog
19 | get() =
20 | project.extensions.getByType().named("libs")
21 |
22 | fun VersionCatalog.version(alias: String): String =
23 | this.findVersion(alias).get().requiredVersion
24 |
25 | fun VersionCatalog.bundle(alias: String): Any =
26 | this.findBundle(alias).get()
27 |
28 | fun VersionCatalog.library(alias: String): Any =
29 | this.findLibrary(alias).get()
30 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/RoomPlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import Plugins
4 | import libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.dependencies
8 | import plugin.util.implementation
9 | import plugin.util.ksp
10 |
11 | class RoomPlugin : Plugin {
12 |
13 | override fun apply(project: Project) {
14 |
15 | project.plugins.apply {
16 | apply(Plugins.Ksp)
17 | }
18 |
19 | project.dependencies {
20 | implementation(project.libs.room.ktx)
21 | implementation(project.libs.room.runtime)
22 | ksp(project.libs.room.compiler)
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/UnitTestPlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import Modules
4 | import libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.dependencies
8 | import plugin.util.testImplementation
9 | import plugin.util.testImplementationProject
10 |
11 |
12 | class UnitTestPlugin : Plugin {
13 |
14 | override fun apply(project: Project) {
15 |
16 | project.dependencies {
17 | testImplementation(project.libs.bundles.test)
18 | testImplementationProject(Modules.Core.TestHelper)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/base/CoreLibraryFeaturePlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin.base
2 |
3 | import Modules
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.dependencies
6 | import plugin.util.implementationProject
7 |
8 | /**
9 | * Plugin containing all common code for library gradle
10 | * ###Applicable in library modules only
11 | */
12 | class CoreLibraryFeaturePlugin : CorePlugin() {
13 | override fun apply(project: Project) {
14 | project.plugins.apply {
15 | apply("com.android.library")
16 | }
17 |
18 | project.dependencies {
19 | with(Modules.Core) {
20 | this@dependencies.implementationProject(Core)
21 | }
22 | }
23 |
24 | super.apply(project)
25 | }
26 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/compose/ComposeCorePlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin.compose
2 |
3 | import com.android.build.gradle.BaseExtension
4 | import libs
5 | import org.gradle.api.Plugin
6 | import org.gradle.api.Project
7 | import org.gradle.kotlin.dsl.dependencies
8 | import plugin.util.PluginConstants
9 | import plugin.util.implementation
10 | import plugin.util.lintChecks
11 |
12 | /**
13 | * Plugin containing setup for adding compose core deps
14 | */
15 | open class ComposeCorePlugin : Plugin {
16 |
17 | override fun apply(project: Project) {
18 |
19 | val androidExtension =
20 | project.extensions.getByName(PluginConstants.ANDROID) as BaseExtension
21 |
22 | androidExtension.apply {
23 | buildFeatures.apply {
24 | compose = true
25 | }
26 | }
27 |
28 | project.dependencies {
29 | implementation(project.libs.bundles.compose)
30 | implementation(platform(project.libs.compose.bom))
31 | lintChecks(project.libs.slack.lint.compose)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/compose/ComposeFeaturePlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin.compose
2 |
3 | import Modules
4 | import libs
5 | import org.gradle.api.Project
6 | import org.gradle.kotlin.dsl.dependencies
7 | import plugin.util.implementation
8 | import plugin.util.implementationProject
9 | import plugin.util.kotlinExtension
10 | import plugin.util.ksp
11 | import plugin.util.libraryExtension
12 |
13 | /**
14 | * Plugin containing setup for adding compose core deps with navigation lib setup
15 | * Also includes coreUi and koin deps
16 | * ###Applicable only for library level module
17 | */
18 | class ComposeFeaturePlugin : ComposeCorePlugin() {
19 |
20 | override fun apply(project: Project) {
21 | project.plugins.apply {
22 | apply("com.google.devtools.ksp")
23 | }
24 |
25 | project.libraryExtension?.apply {
26 | libraryVariants.all {
27 | project.kotlinExtension.sourceSets.getByName(name) {
28 | kotlin.srcDir("build/generated/ksp/$name/kotlin")
29 | }
30 | }
31 | }
32 |
33 | project.dependencies {
34 | implementation(project.libs.compose.destinations)
35 | ksp(project.libs.compose.destinations.ksp)
36 | implementation(project.libs.compose.orbit.mvi)
37 | implementation(project.libs.koin.compose)
38 |
39 | with(Modules.Core) {
40 | this@dependencies.implementationProject(DesignSystem)
41 | this@dependencies.implementationProject(CoreUi)
42 | }
43 | }
44 |
45 | super.apply(project)
46 | }
47 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/util/PluginConstants.kt:
--------------------------------------------------------------------------------
1 | package plugin.util
2 |
3 | internal object PluginConstants {
4 | const val IMPLEMENTATION = "implementation"
5 | const val ANDROID = "android"
6 | const val PATH = "path"
7 | const val KOTLIN = "kotlin"
8 | const val KSP = "ksp"
9 | const val TEST_IMPLEMENTATION = "testImplementation"
10 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/plugin/util/PluginExtensions.kt:
--------------------------------------------------------------------------------
1 | package plugin.util
2 |
3 | import com.android.build.gradle.AppExtension
4 | import com.android.build.gradle.LibraryExtension
5 | import org.gradle.api.Project
6 | import org.gradle.api.artifacts.Dependency
7 | import org.gradle.api.plugins.ExtensionAware
8 | import org.gradle.kotlin.dsl.DependencyHandlerScope
9 | import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
10 |
11 | internal val Project.kotlinExtension
12 | get() = (this as ExtensionAware).extensions.getByName(PluginConstants.KOTLIN) as KotlinAndroidProjectExtension
13 |
14 | internal val Project.libraryExtension
15 | get() = project.extensions.getByName(PluginConstants.ANDROID) as? LibraryExtension
16 |
17 | internal val Project.appExtension
18 | get() = project.extensions.getByName(PluginConstants.ANDROID) as? AppExtension
19 |
20 | internal fun DependencyHandlerScope.implementation(dependency: Any) {
21 | add(PluginConstants.IMPLEMENTATION, dependency)
22 | }
23 |
24 | internal fun DependencyHandlerScope.lintChecks(dependency: Any) {
25 | add(PluginConstants.IMPLEMENTATION, dependency)
26 | }
27 |
28 | internal fun DependencyHandlerScope.testImplementation(dependencyNotation: Any): Dependency? =
29 | add(PluginConstants.TEST_IMPLEMENTATION, dependencyNotation)
30 |
31 | internal fun DependencyHandlerScope.testImplementationProject(dependency: String) {
32 | add(
33 | PluginConstants.TEST_IMPLEMENTATION,
34 | project(mapOf(PluginConstants.PATH to dependency))
35 | )
36 | }
37 |
38 | internal fun DependencyHandlerScope.implementationProject(dependency: String) {
39 | add(
40 | PluginConstants.IMPLEMENTATION,
41 | project(mapOf(PluginConstants.PATH to dependency))
42 | )
43 | }
44 |
45 | internal fun DependencyHandlerScope.ksp(dependency: Any) {
46 | add(PluginConstants.KSP, dependency)
47 | }
--------------------------------------------------------------------------------
/core/agenda-manger/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/agenda-manger/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.RoomDatabase)
4 | id(Plugins.UnitTestPlugin)
5 | }
6 |
7 | android {
8 | namespace = "pseudoankit.droid.agendamanger"
9 | }
10 |
11 | dependencies {
12 | implementation(projects.core.alarmManager)
13 | implementation(projects.core.notificationManager)
14 | }
15 |
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/data/local/dao/ReminderDao.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.data.local.dao
2 |
3 | import androidx.room.*
4 | import kotlinx.coroutines.flow.Flow
5 | import pseudoankit.droid.agendamanger.data.local.entity.ReminderEntity
6 | import java.time.LocalDate
7 |
8 | @Dao
9 | interface ReminderDao {
10 |
11 | @Query("Select * from reminderentity")
12 | fun getReminders(): List
13 |
14 | @Query("Select * from reminderentity")
15 | fun getRemindersFlow(): Flow>
16 |
17 | @Query("Select * from reminderentity where date= :date")
18 | fun getRemindersFlow(date: LocalDate): Flow>
19 |
20 | @Query("Select * from reminderentity where id=:id")
21 | fun getReminder(id: Long): ReminderEntity
22 |
23 | @Insert(onConflict = OnConflictStrategy.REPLACE)
24 | suspend fun insert(item: ReminderEntity)
25 |
26 | @Update
27 | suspend fun update(item: ReminderEntity)
28 |
29 | @Delete
30 | suspend fun delete(item: ReminderEntity)
31 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/data/local/entity/ReminderEntity.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.data.local.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
6 | import java.time.LocalDate
7 | import java.time.LocalTime
8 |
9 | @Entity
10 | data class ReminderEntity(
11 | val title: String?,
12 | val remindAllDay: Boolean?,
13 | val date: LocalDate?,
14 | val time: LocalTime?,
15 | val repeatInterval: AgendaItem.Reminder.RepeatInterval?,
16 | val completed: Boolean?,
17 | @PrimaryKey val id: Long
18 | )
19 |
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/data/repository/AgendaRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.data.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.combine
5 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
6 | import pseudoankit.droid.agendamanger.domain.repository.AgendaRepository
7 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepository
8 | import java.time.LocalDate
9 |
10 | // TODO sorting for all agenda types
11 | internal class AgendaRepositoryImpl(
12 | private val reminderRepository: ReminderRepository
13 | ) : AgendaRepository {
14 |
15 | override fun getAllSavedItemFlow(selectedDate: LocalDate?): Flow> {
16 | return combine(
17 | reminderRepository.getReminders(selectedDate)
18 | ) { items ->
19 | items[0]
20 | }
21 | }
22 |
23 | override fun getAllSavedItem(): List {
24 | val reminderItems = reminderRepository.getReminders()
25 | // combine others
26 |
27 | return reminderItems
28 | }
29 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/di/AgendaManagerModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.di
2 |
3 | import org.koin.dsl.module
4 | import pseudoankit.droid.agendamanger.data.repository.AgendaRepositoryImpl
5 | import pseudoankit.droid.agendamanger.data.repository.ReminderRepositoryImpl
6 | import pseudoankit.droid.agendamanger.domain.repository.AgendaRepository
7 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepository
8 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepositoryInternal
9 | import pseudoankit.droid.agendamanger.domain.usecase.reminder.SaveReminderUseCase
10 | import pseudoankit.droid.agendamanger.domain.usecase.reminder.TriggerAlarmUseCase
11 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlowInstance
12 |
13 | // TODO move to individual screen
14 | object AgendaManagerModule {
15 |
16 | operator fun invoke() = module {
17 | single { SaveReminderUseCase(UpdateAppWidgetFlowInstance) }
18 | single { TriggerAlarmUseCase(get()) }
19 |
20 | single { ReminderRepositoryImpl(get()) }
21 | single { ReminderRepositoryImpl(get()) }
22 | single { AgendaRepositoryImpl(get()) }
23 | }
24 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/mapper/ReminderMapper.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.mapper
2 |
3 | import pseudoankit.droid.agendamanger.data.local.entity.ReminderEntity
4 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
5 | import pseudoankit.droid.core.model.TaskyDate
6 | import pseudoankit.droid.core.model.TaskyTime
7 | import pseudoankit.droid.core.util.extension.orFalse
8 | import pseudoankit.droid.core.util.extension.orNow
9 | import pseudoankit.droid.core.util.extension.orToday
10 |
11 | internal object ReminderMapper {
12 |
13 | val AgendaItem.Reminder.mapToEntity
14 | get() = ReminderEntity(
15 | title = title,
16 | remindAllDay = time == AgendaItem.Reminder.Time.AllDay,
17 | date = date.value,
18 | time = when (time) {
19 | AgendaItem.Reminder.Time.AllDay -> null
20 | is AgendaItem.Reminder.Time.Time -> time.value.value
21 | },
22 | repeatInterval = repeatInterval,
23 | id = id,
24 | completed = completed
25 | )
26 |
27 | val ReminderEntity.mapToDomain
28 | get() = AgendaItem.Reminder(
29 | title = title.orEmpty(),
30 | date = TaskyDate(date.orToday),
31 | time = if (time == null) {
32 | AgendaItem.Reminder.Time.AllDay
33 | } else {
34 | AgendaItem.Reminder.Time.Time(TaskyTime(time.orNow))
35 | },
36 | repeatInterval = repeatInterval ?: AgendaItem.Reminder.RepeatInterval.DoNotRepeat,
37 | id = id,
38 | completed = completed.orFalse
39 | )
40 |
41 | val List.sortByAscDateTime
42 | get() = sortedWith { o1, o2 ->
43 | if (o1.date.value > o2.date.value) return@sortedWith 1
44 | if (o1.date.value < o2.date.value) return@sortedWith -1
45 | o1.time.seconds - o2.time.seconds
46 | }
47 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/model/AgendaTypes.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.model
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 | import pseudoankit.droid.core.testtag.AgendaTestTag
6 |
7 | sealed interface AgendaTypes {
8 | data class Reminder(val action: Action) : AgendaTypes
9 | data class Task(val action: Action) : AgendaTypes
10 | data class Event(val action: Action) : AgendaTypes
11 |
12 | @Parcelize
13 | sealed class Action : Parcelable {
14 | data class Edit(val id: Long) : Action()
15 | object Create : Action()
16 | }
17 |
18 | val testTag
19 | get() = when (this) {
20 | is Event -> AgendaTestTag.event
21 | is Reminder -> AgendaTestTag.reminder
22 | is Task -> AgendaTestTag.task
23 | }
24 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/repository/AgendaRepository.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
5 | import java.time.LocalDate
6 |
7 | interface AgendaRepository {
8 |
9 | fun getAllSavedItemFlow(selectedDate: LocalDate? = null): Flow>
10 |
11 | fun getAllSavedItem(): List
12 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/repository/ReminderRepository.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.repository
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
5 | import java.time.LocalDate
6 |
7 | interface ReminderRepository {
8 | fun getReminders(date: LocalDate?): Flow>
9 | fun getReminders(): List
10 | fun getReminder(id: Long): AgendaItem.Reminder
11 | suspend fun delete(payload: AgendaItem.Reminder)
12 | }
13 |
14 | internal interface ReminderRepositoryInternal {
15 | suspend fun save(payload: AgendaItem.Reminder)
16 | suspend fun update(payload: AgendaItem.Reminder)
17 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/usecase/reminder/GetSavedAgendaItemsUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.usecase.reminder
2 |
3 | import kotlinx.collections.immutable.ImmutableList
4 | import kotlinx.collections.immutable.toImmutableList
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.map
7 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
8 | import pseudoankit.droid.agendamanger.domain.repository.AgendaRepository
9 | import pseudoankit.droid.core.model.TaskyDate
10 |
11 | class GetSavedAgendaItemsUseCase(
12 | private val agendaRepository: AgendaRepository
13 | ) {
14 |
15 | operator fun invoke(selectedDate: TaskyDate): Flow> {
16 | return agendaRepository.getAllSavedItemFlow(selectedDate.value)
17 | .map { it.toImmutableList() }
18 | }
19 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/usecase/reminder/SaveReminderUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.usecase.reminder
2 |
3 | import kotlinx.coroutines.coroutineScope
4 | import kotlinx.coroutines.launch
5 | import org.koin.core.component.KoinComponent
6 | import org.koin.core.component.inject
7 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
8 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepositoryInternal
9 | import pseudoankit.droid.core.util.TaskyResult
10 | import pseudoankit.droid.core.util.extension.safeCall
11 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlow
12 |
13 | class SaveReminderUseCase(
14 | private val updateAppWidgetFlow: UpdateAppWidgetFlow
15 | ) : KoinComponent {
16 |
17 | private val repository: ReminderRepositoryInternal by inject()
18 | private val triggerAlarmUseCase: TriggerAlarmUseCase by inject()
19 |
20 | // TODO: cancel notification if item completed
21 | suspend operator fun invoke(
22 | payload: AgendaItem.Reminder,
23 | alarmDeepLink: String
24 | ): TaskyResult = safeCall(
25 | block = {
26 | coroutineScope {
27 | launch {
28 | triggerAlarmUseCase(
29 | payload,
30 | alarmDeepLink
31 | )
32 | }
33 | launch {
34 | repository.save(payload)
35 | }
36 | updateAppWidgetFlow.emit(Unit)
37 | }
38 | TaskyResult.Success(Unit)
39 | },
40 | onError = {
41 | TaskyResult.Error(it)
42 | }
43 | )
44 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/domain/usecase/reminder/TriggerAlarmUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.domain.usecase.reminder
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
4 | import pseudoankit.droid.alarm_scheduler.domain.AlarmScheduler
5 | import pseudoankit.droid.alarm_scheduler.domain.model.Alarm
6 | import pseudoankit.droid.notification_manager.TaskyNotifierConfig
7 | import java.time.LocalDateTime
8 | import java.time.LocalTime
9 |
10 | internal class TriggerAlarmUseCase(
11 | private val alarmScheduler: AlarmScheduler
12 | ) {
13 |
14 | operator fun invoke(payload: AgendaItem.Reminder, alarmDeepLink: String) {
15 | val time = when (payload.time) {
16 | AgendaItem.Reminder.Time.AllDay -> LocalTime.NOON
17 | is AgendaItem.Reminder.Time.Time -> payload.time.value.value
18 | }
19 |
20 | val alarm = Alarm(
21 | localDateTime = LocalDateTime.of(
22 | payload.date.value.year,
23 | payload.date.value.month,
24 | payload.date.value.dayOfMonth,
25 | time.hour,
26 | time.minute,
27 | time.second
28 | ),
29 | title = payload.title,
30 | navigationUrl = alarmDeepLink,
31 | id = payload.id,
32 | source = TaskyNotifierConfig.Source.Reminder
33 | )
34 |
35 | if (payload.completed) {
36 | alarmScheduler.cancel(alarm)
37 | } else {
38 | alarmScheduler.schedule(alarm)
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/main/java/pseudoankit/droid/agendamanger/util/AgendaConstants.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.util
2 |
3 | object AgendaConstants {
4 | val id = System.currentTimeMillis()
5 | }
--------------------------------------------------------------------------------
/core/agenda-manger/src/test/java/pseudoankit/droid/agendamanger/data/repository/AgendaRepositoryImplTest.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.data.repository
2 |
3 | import io.mockk.coEvery
4 | import io.mockk.mockk
5 | import org.junit.Test
6 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
7 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepository
8 | import kotlin.test.assertEquals
9 |
10 |
11 | internal class AgendaRepositoryImplTest {
12 |
13 | private val reminderRepository: ReminderRepository = mockk()
14 | private val repository = AgendaRepositoryImpl(reminderRepository)
15 |
16 |
17 | @Test
18 | fun `getAllSavedItem should return data from reminderRepository`() {
19 | val expected = listOf(
20 | AgendaItem.Reminder()
21 | )
22 | coEvery { reminderRepository.getReminders() } returns expected
23 |
24 | val actual = repository.getAllSavedItem()
25 |
26 | assertEquals(expected, actual)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/agenda-manger/src/test/java/pseudoankit/droid/agendamanger/util/TestUtil.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.agendamanger.util
2 |
3 | import pseudoankit.droid.agendamanger.data.local.entity.ReminderEntity
4 |
5 | val reminderEntity = ReminderEntity(
6 | null,
7 | null,
8 | null,
9 | null,
10 | null,
11 | null,
12 | 1
13 | )
14 |
--------------------------------------------------------------------------------
/core/alarm-manager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/alarm-manager/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | }
4 |
5 | android {
6 | namespace = "pseudoankit.droid.alarm_scheduler"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.notificationManager)
11 | }
--------------------------------------------------------------------------------
/core/alarm-manager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/core/alarm-manager/src/main/java/pseudoankit/droid/alarm_scheduler/data/AlarmReceiver.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.alarm_scheduler.data
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import org.koin.java.KoinJavaComponent.inject
7 | import pseudoankit.droid.alarm_scheduler.domain.model.Alarm
8 | import pseudoankit.droid.core.logger.TaskyLogger
9 | import pseudoankit.droid.core.util.extension.getParcelableData
10 | import pseudoankit.droid.notification_manager.TaskyNotifier
11 | import pseudoankit.droid.notification_manager.TaskyNotifierConfig
12 |
13 | internal class AlarmReceiver : BroadcastReceiver() {
14 |
15 | private val taskyNotifier: TaskyNotifier by inject(TaskyNotifier::class.java)
16 |
17 | companion object {
18 | private const val ALARM_ARG = "alarm"
19 | fun instance(context: Context, alarm: Alarm): Intent {
20 | return Intent(context, AlarmReceiver::class.java).apply {
21 | putExtra(ALARM_ARG, alarm)
22 | }
23 | }
24 | }
25 |
26 | override fun onReceive(context: Context?, intent: Intent?) {
27 | val alarm = intent?.extras?.getParcelableData(ALARM_ARG) ?: return
28 |
29 | TaskyLogger.info("triggering alarm", alarm.toString())
30 | taskyNotifier.displayNotification(
31 | TaskyNotifierConfig(
32 | description = alarm.title,
33 | priority = TaskyNotifierConfig.Priority.High,
34 | source = alarm.source,
35 | navigationUrl = alarm.navigationUrl
36 | )
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/alarm-manager/src/main/java/pseudoankit/droid/alarm_scheduler/data/AndroidAlarmScheduler.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.alarm_scheduler.data
2 |
3 | import android.app.AlarmManager
4 | import android.app.PendingIntent
5 | import android.content.Context
6 | import android.content.Intent
7 | import pseudoankit.droid.alarm_scheduler.domain.AlarmScheduler
8 | import pseudoankit.droid.alarm_scheduler.domain.model.Alarm
9 | import pseudoankit.droid.core.logger.TaskyLogger
10 |
11 | internal class AndroidAlarmScheduler(
12 | private val context: Context
13 | ) : AlarmScheduler {
14 |
15 | private val alarmManager = context.getSystemService(AlarmManager::class.java)
16 |
17 | override fun schedule(alarm: Alarm) {
18 | TaskyLogger.info("scheduling alarm", alarm.toString())
19 |
20 | createPendingIntent(alarm, intent = {
21 | AlarmReceiver.instance(context, alarm)
22 | })?.let {
23 | alarmManager.setExactAndAllowWhileIdle(
24 | AlarmManager.RTC_WAKEUP,
25 | alarm.timeInMillis,
26 | it
27 | )
28 | }
29 | }
30 |
31 | override fun cancel(alarm: Alarm) {
32 | TaskyLogger.info("cancelling alarm", alarm.toString())
33 | createPendingIntent(alarm, intent = {
34 | AlarmReceiver.instance(context, alarm)
35 | })?.let {
36 | alarmManager.cancel(
37 | it
38 | )
39 | }
40 | }
41 |
42 | private fun createPendingIntent(alarm: Alarm, intent: () -> Intent): PendingIntent? {
43 | return PendingIntent.getBroadcast(
44 | context,
45 | alarm.id.toInt(),
46 | intent(),
47 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
48 | )
49 | }
50 | }
--------------------------------------------------------------------------------
/core/alarm-manager/src/main/java/pseudoankit/droid/alarm_scheduler/di/AlarmManagerModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.alarm_scheduler.di
2 |
3 | import org.koin.dsl.module
4 | import pseudoankit.droid.alarm_scheduler.data.AndroidAlarmScheduler
5 | import pseudoankit.droid.alarm_scheduler.domain.AlarmScheduler
6 |
7 | object AlarmManagerModule {
8 |
9 | operator fun invoke() = module {
10 | single { AndroidAlarmScheduler(get()) }
11 | }
12 | }
--------------------------------------------------------------------------------
/core/alarm-manager/src/main/java/pseudoankit/droid/alarm_scheduler/domain/AlarmScheduler.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.alarm_scheduler.domain
2 |
3 | import pseudoankit.droid.alarm_scheduler.domain.model.Alarm
4 |
5 | interface AlarmScheduler {
6 | fun schedule(alarm: Alarm)
7 | fun cancel(alarm: Alarm)
8 | }
--------------------------------------------------------------------------------
/core/alarm-manager/src/main/java/pseudoankit/droid/alarm_scheduler/domain/model/Alarm.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.alarm_scheduler.domain.model
2 |
3 | import android.os.Build
4 | import android.os.Parcelable
5 | import kotlinx.parcelize.Parcelize
6 | import pseudoankit.droid.notification_manager.TaskyNotifierConfig
7 | import java.time.LocalDateTime
8 | import java.time.ZoneId
9 | import java.time.ZonedDateTime
10 | import java.util.*
11 |
12 | @Parcelize
13 | data class Alarm(
14 | val localDateTime: LocalDateTime,
15 | val title: String,
16 | val navigationUrl: String,
17 | val id: Long,
18 | val source: TaskyNotifierConfig.Source
19 | ) : Parcelable {
20 |
21 | val timeInMillis get() = localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond().times(1000)
22 | }
23 |
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeCore)
4 | }
5 |
6 | android {
7 | namespace = "pseudoankit.droid.app_shortcuts_n_widgets"
8 | }
9 |
10 | dependencies {
11 | implementation(libs.glance.appwidget)
12 | implementation(libs.glance)
13 | implementation(libs.androidx.appShortcut)
14 |
15 | implementation(projects.core.agendaManger)
16 | implementation(projects.core.designSystem)
17 | implementation(projects.core.coreUi)
18 | implementation(projects.core.preferencesManager)
19 | }
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/java/pseudoankit/droid/WidgetsNShortcutsManager.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid
2 |
3 | import android.content.Context
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.flow.MutableSharedFlow
7 | import kotlinx.coroutines.launch
8 | import org.koin.java.KoinJavaComponent.inject
9 | import pseudoankit.droid.app_shortcuts.TaskyShortCutManager
10 | import pseudoankit.droid.app_widgets.agenda_items.AgendaItemAppWidgetReceiver
11 | import pseudoankit.droid.core.logger.TaskyLogger
12 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlow
13 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlowNamed
14 |
15 | object WidgetsNShortcutsManager {
16 |
17 | private val widgetUpdateFlow: UpdateAppWidgetFlow by inject(
18 | MutableSharedFlow::class.java,
19 | UpdateAppWidgetFlowNamed
20 | )
21 |
22 | fun initialize(context: Context) {
23 | TaskyShortCutManager.initialize(context)
24 |
25 | CoroutineScope(Dispatchers.IO).launch {
26 | widgetUpdateFlow.collect {
27 | TaskyLogger.info("updating widget")
28 | AgendaItemAppWidgetReceiver.sendAppsUpdatedBroadcast(context)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/java/pseudoankit/droid/app_shortcuts/TaskyShortCutManager.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.app_shortcuts
2 |
3 | import android.content.Context
4 | import androidx.annotation.DrawableRes
5 | import androidx.core.content.pm.ShortcutInfoCompat
6 | import androidx.core.content.pm.ShortcutManagerCompat
7 | import androidx.core.graphics.drawable.IconCompat
8 | import pseudoankit.droid.core.deeplink.DeepLinkUtil
9 | import pseudoankit.droid.core.deeplink.TaskyDeeplink
10 | import pseudoankit.droid.unify.utils.UnifyDrawable
11 |
12 | internal object TaskyShortCutManager {
13 |
14 | fun initialize(context: Context) {
15 | addShortCut(
16 | context = context,
17 | id = "Reminder",
18 | label = "Add Reminder",
19 | icon = UnifyDrawable.ic_notification,
20 | navigationDeepLink = TaskyDeeplink.reminder
21 | )
22 |
23 | /* addShortCut(
24 | context = context,
25 | id = "Event",
26 | label = "Add Event",
27 | icon = UnifyDrawable.ic_calendar,
28 | navigationDeepLink = TaskyDeeplink.reminder
29 | )
30 |
31 | addShortCut(
32 | context = context,
33 | id = "Task",
34 | label = "Add Task",
35 | icon = UnifyDrawable.ic_task,
36 | navigationDeepLink = TaskyDeeplink.reminder
37 | )*/
38 | }
39 |
40 | private fun addShortCut(
41 | context: Context,
42 | id: String,
43 | label: String,
44 | longLabel: String = "",
45 | @DrawableRes icon: Int,
46 | navigationDeepLink: String
47 | ) {
48 | val shortcut = ShortcutInfoCompat.Builder(context, id)
49 | .setShortLabel(label)
50 | .setLongLabel(longLabel)
51 | .setIcon(IconCompat.createWithResource(context, icon))
52 | .setIntent(DeepLinkUtil.createDeeplinkIntent(navigationDeepLink))
53 | .build()
54 |
55 | ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
56 | }
57 | }
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/java/pseudoankit/droid/app_widgets/agenda_items/AgendaItemAppWidgetReceiver.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.app_widgets.agenda_items
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.glance.appwidget.GlanceAppWidget
8 | import androidx.glance.appwidget.GlanceAppWidgetReceiver
9 |
10 | class AgendaItemAppWidgetReceiver : GlanceAppWidgetReceiver() {
11 |
12 | override val glanceAppWidget: GlanceAppWidget
13 | get() = AgendaItemsAppWidget
14 |
15 | override fun onReceive(context: Context, intent: Intent) {
16 | super.onReceive(context, intent)
17 |
18 | val appWidgetManager = AppWidgetManager.getInstance(context)
19 | val componentName =
20 | ComponentName(context.packageName, checkNotNull(javaClass.canonicalName))
21 | onUpdate(
22 | context,
23 | appWidgetManager,
24 | appWidgetManager.getAppWidgetIds(componentName),
25 | )
26 | }
27 |
28 | companion object {
29 | fun sendAppsUpdatedBroadcast(context: Context) {
30 | context.sendBroadcast(
31 | Intent(context, AgendaItemAppWidgetReceiver::class.java)
32 | )
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/java/pseudoankit/droid/app_widgets/util/WidgetDeeplinkProvider.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.app_widgets.util
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
4 |
5 | interface WidgetDeeplinkProvider {
6 | fun agendaDetailRoute(action: AgendaTypes): String
7 | }
8 |
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/java/pseudoankit/droid/di/WidgetsNShortcutsModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.di
2 |
3 | import kotlinx.coroutines.flow.MutableSharedFlow
4 | import org.koin.dsl.module
5 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlow
6 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlowNamed
7 |
8 | object WidgetsNShortcutsModule {
9 | operator fun invoke() = module {
10 | single(UpdateAppWidgetFlowNamed) { MutableSharedFlow() }
11 | }
12 | }
--------------------------------------------------------------------------------
/core/app-shortcuts-n-widgets/src/main/res/xml/tasky_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/core/core-ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/core-ui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeCore)
4 | }
5 |
6 | android {
7 | namespace = "pseudoankit.droid.coreui"
8 | }
9 |
10 | dependencies {
11 | implementation(projects.core.designSystem)
12 | implementation(libs.compose.destinations)
13 | implementation(libs.compose.orbit.mvi)
14 | }
--------------------------------------------------------------------------------
/core/core-ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/deeplink/DeepLinkReciever.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.deeplink
2 |
3 | import android.net.Uri
4 | import androidx.navigation.NavController
5 | import pseudoankit.droid.core.logger.logError
6 |
7 | fun NavController.navigateViaDeepLink(deepLink: String) {
8 | navigateViaDeepLink(Uri.parse(deepLink))
9 | }
10 |
11 | fun NavController.navigateViaDeepLink(uri: Uri?) = try {
12 | navigate(uri!!)
13 | } catch (e: IllegalArgumentException) {
14 | e.printStackTrace()
15 | logError("deeplink [${uri}] failed with error msg ${e.message}")
16 | }
17 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/destination/TaskyDestinationStyle.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.destination
2 |
3 | import androidx.compose.ui.window.DialogProperties
4 | import com.ramcosta.composedestinations.spec.DestinationStyle
5 |
6 | object TaskyDestinationStyle {
7 |
8 | object Dialog : DestinationStyle.Dialog {
9 | override val properties: DialogProperties
10 | get() = DialogProperties(
11 | usePlatformDefaultWidth = false
12 | )
13 | }
14 | }
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/koin/ComposeKoinModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.koin
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import pseudoankit.droid.core.koin.BaseKoinModule
6 |
7 | /**
8 | * method to manage loading / unloading of koin modules
9 | * @param[module] requires instance of [BaseKoinModule] to load and unload modules
10 | */
11 | @Composable
12 | fun BaseKoinModule.load(content: @Composable () -> Unit) {
13 | loadModules()
14 | DisposableEffect(Unit) {
15 | onDispose {
16 | unloadModules()
17 | }
18 | }
19 | content()
20 | }
21 |
22 | /**
23 | * method to manage loading / unloading of koin modules
24 | * @param[module] requires instance of [BaseKoinModule] to load and unload modules
25 | */
26 | @Composable
27 | fun BaseKoinModule.load() {
28 | loadModules()
29 | DisposableEffect(Unit) {
30 | onDispose {
31 | unloadModules()
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/model/TextFieldUiConfig.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.model
2 |
3 | import pseudoankit.droid.core.util.TextResource
4 |
5 | data class TextFieldUiConfig(
6 | val value: String,
7 | val errorMessage: TextResource?
8 | )
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/util/extension/ContextExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.util.extension
2 |
3 | import android.app.Activity
4 | import android.content.ActivityNotFoundException
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.net.Uri
8 | import android.provider.Settings
9 | import android.widget.Toast
10 | import pseudoankit.droid.core.deeplink.DeepLinkUtil
11 | import pseudoankit.droid.core.util.TextResource
12 | import pseudoankit.droid.core.util.TextResource.Companion.asString
13 |
14 | fun Context.openDeeplink(deeplink: String) {
15 | startActivity(DeepLinkUtil.createDeeplinkIntent(deeplink))
16 | }
17 |
18 | fun Context.finish() {
19 | (this as? Activity)?.finish()
20 | }
21 |
22 | fun Context.navigateToSettings() = try {
23 | startActivity(
24 | Intent().apply {
25 | action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
26 | data = Uri.fromParts("package", packageName, null)
27 | }
28 | )
29 | } catch (e: ActivityNotFoundException) {
30 | showToast("Oops! Something went wrong, Long press app icon -> click on info -> grant permission")
31 | e.printStackTrace()
32 | }
33 |
34 | fun Context.showToast(message: String) {
35 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
36 | }
37 |
38 | fun Context.showToast(message: TextResource) {
39 | showToast(message.asString(this))
40 | }
41 |
42 | fun Context.toastNotImplemented() {
43 | showToast("This feature is not implemented yet!")
44 | }
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/util/extension/ModifierExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.util.extension
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.composed
5 | import androidx.compose.ui.layout.layout
6 | import androidx.compose.ui.unit.Dp
7 | import androidx.compose.ui.unit.dp
8 |
9 | fun Modifier.placeWhileIgnoringHorizontalParentPadding(paddingToIgnore: Dp = 16.dp) = composed {
10 | layout { measurable, constraints ->
11 | val placeable = measurable.measure(
12 | constraints.copy(
13 | maxWidth = paddingToIgnore.roundToPx() + constraints.maxWidth + paddingToIgnore.roundToPx(), //add the end padding 16.dp
14 | )
15 | )
16 | layout(placeable.width, placeable.height) {
17 | placeable.place(0, 0)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/util/extension/NavControllerExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.util.extension
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavOptionsBuilder
5 |
6 | fun NavController.clearStack(navOptionsBuilder: NavOptionsBuilder) {
7 | navOptionsBuilder.popUpTo(this.graph.id) {
8 | inclusive = true
9 | }
10 | }
11 |
12 | fun NavController.clearStack() {
13 | popBackStack(this.graph.id, true)
14 | }
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/util/extension/StateExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.util.extension
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.MutableState
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 |
8 | @Composable
9 | fun rememberMutableStateOf(value: T, vararg keys: Any): MutableState = remember(*keys) {
10 | mutableStateOf(value)
11 | }
12 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/java/pseudoankit/droid/coreui/util/extension/TextResourceExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.coreui.util.extension
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.platform.LocalContext
5 | import pseudoankit.droid.core.util.TextResource
6 | import pseudoankit.droid.core.util.TextResource.Companion.asString
7 |
8 | @Composable
9 | fun TextResource?.asString(): String {
10 | return this.asString(LocalContext.current)
11 | }
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/drawable/ic_splash.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/core/core-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/core/core-ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/core/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.AndroidLibrary)
3 | id(Plugins.Core)
4 | }
5 |
6 | android {
7 | namespace = "pseudoankit.droid.core"
8 | }
9 |
--------------------------------------------------------------------------------
/core/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/coroutine/CoroutineUtil.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.coroutine
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.Job
6 | import kotlinx.coroutines.launch
7 | import kotlin.coroutines.CoroutineContext
8 |
9 | fun launchCoroutine(
10 | dispatcher: CoroutineContext = Dispatchers.IO,
11 | block: suspend CoroutineScope.() -> Unit
12 | ): Job {
13 | return CoroutineScope(dispatcher).launch {
14 | block()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/deeplink/DeepLinkUtil.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.deeplink
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 |
6 | object DeepLinkUtil {
7 | fun createDeeplinkIntent(deeplinkUri: String): Intent {
8 | return Intent(Intent.ACTION_VIEW).apply {
9 | data = Uri.parse(deeplinkUri)
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/deeplink/TaskyDeeplink.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.deeplink
2 |
3 | /**
4 | * Global deeplink file
5 | * Preferred way to create deeplink is via [pseudoankit.droid.navigation.deeplink.DeepLinkProvider]
6 | */
7 | object TaskyDeeplink {
8 | object Path {
9 | object Reminder {
10 | const val action = "{action}"
11 | }
12 | }
13 |
14 | const val login = "tasky://login"
15 | const val registration = "tasky://registration"
16 | const val home = "tasky://home"
17 | const val reminder = "tasky://reminder/${Path.Reminder.action}"
18 | const val agendaSelection = "tasky://agendaSelection"
19 | const val profile = "tasky://profile"
20 | }
21 |
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/di/CoreModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.di
2 |
3 | import org.koin.dsl.module
4 | import pseudoankit.droid.core.dispatcher.DispatcherProvider
5 | import pseudoankit.droid.core.dispatcher.DispatcherProviderImpl
6 |
7 | object CoreModule {
8 |
9 | operator fun invoke() = module {
10 | single { DispatcherProviderImpl() }
11 | }
12 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/dispatcher/DispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.dispatcher
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.CoroutineScope
5 |
6 | interface DispatcherProvider {
7 | val io: CoroutineDispatcher
8 | val default: CoroutineDispatcher
9 | suspend fun switchToIo(block: suspend CoroutineScope.() -> T): T
10 | suspend fun switchToDefault(block: suspend CoroutineScope.() -> T): T
11 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/dispatcher/DispatcherProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.dispatcher
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 |
8 | internal class DispatcherProviderImpl : DispatcherProvider {
9 |
10 | override val io: CoroutineDispatcher
11 | get() = Dispatchers.IO
12 |
13 | override val default: CoroutineDispatcher
14 | get() = Dispatchers.Default
15 |
16 | override suspend fun switchToDefault(block: suspend CoroutineScope.() -> T): T {
17 | return withContext(context = default, block = block)
18 | }
19 |
20 | override suspend fun switchToIo(block: suspend CoroutineScope.() -> T): T {
21 | return withContext(context = io, block = block)
22 | }
23 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/koin/BaseKoinModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.koin
2 |
3 | import org.koin.core.context.loadKoinModules
4 | import org.koin.core.context.unloadKoinModules
5 | import org.koin.core.module.Module
6 |
7 | abstract class BaseKoinModule {
8 | private var isLoaded = false
9 |
10 | abstract val modules: Module
11 |
12 | fun loadModules() {
13 | if (isLoaded) return
14 |
15 | isLoaded = true
16 | loadKoinModules(modules)
17 | }
18 |
19 | fun unloadModules() {
20 | if (isLoaded.not()) return
21 |
22 | isLoaded = false
23 | unloadKoinModules(modules)
24 | }
25 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/logger/TaskyLogger.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.logger
2 |
3 | import android.util.Log
4 |
5 | object TaskyLogger {
6 |
7 | private const val INTERNAL_TAG = "TASKY LOGS"
8 |
9 | fun info(vararg value: Any, tag: String = "") {
10 | Log.i("$INTERNAL_TAG $tag", value.joinToString(", "))
11 | }
12 |
13 | fun error(vararg value: Any, tag: String = "") {
14 | Log.e("$INTERNAL_TAG $tag", value.joinToString(", "))
15 | }
16 | }
17 |
18 | fun logInfo(vararg value: Any, tag: String = "") {
19 | TaskyLogger.info(*value, tag)
20 | }
21 |
22 | fun logError(vararg value: Any, tag: String = "") {
23 | TaskyLogger.error(*value, tag)
24 | }
25 |
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/model/TaskyDate.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.model
2 |
3 | import android.os.Parcelable
4 | import androidx.compose.runtime.Stable
5 | import kotlinx.parcelize.Parcelize
6 | import java.time.LocalDate
7 |
8 | @Parcelize
9 | @Stable
10 | @JvmInline
11 | value class TaskyDate(val value: LocalDate) : Parcelable {
12 |
13 | companion object {
14 | val Today: TaskyDate get() = TaskyDate(LocalDate.now())
15 | }
16 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/model/TaskyTime.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.model
2 |
3 | import android.os.Parcelable
4 | import androidx.compose.runtime.Stable
5 | import kotlinx.parcelize.Parcelize
6 | import java.time.LocalTime
7 |
8 | @Parcelize
9 | @Stable
10 | @JvmInline
11 | value class TaskyTime(val value: LocalTime) : Parcelable {
12 |
13 | companion object {
14 | val Now = TaskyTime(LocalTime.now())
15 | }
16 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/testtag/AgendaTestTag.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.testtag
2 |
3 | object AgendaTestTag {
4 | const val reminder = "reminder"
5 | const val task = "task"
6 | const val event = "event"
7 | const val save = "save"
8 | }
9 |
10 | object ReminderTestTag {
11 | const val edtRemindMe = "edt_remind_me"
12 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/testtag/AuthTestTag.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.testtag
2 |
3 | object AuthTestTag {
4 | const val email = "email"
5 | const val password = "password"
6 | const val loginBtn = "login_btn"
7 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/testtag/HomeTestTag.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.testtag
2 |
3 | object HomeTestTag {
4 | const val monthDatesHorizontalList = "month_date_items"
5 | const val agendaItemList = "agenda_items"
6 | const val fab = "fab"
7 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/JsonSerializer.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util
2 |
3 | import kotlinx.serialization.json.Json
4 |
5 | val defaultJsonSerializer = Json {
6 | isLenient = true
7 | explicitNulls = true
8 | ignoreUnknownKeys = true
9 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/TaskyResult.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util
2 |
3 | sealed interface TaskyResult {
4 | data class Success(val data: T) : TaskyResult
5 | data class Error(val error: TextResource) : TaskyResult
6 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/TextResource.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util
2 |
3 | import android.content.Context
4 | import androidx.compose.runtime.Stable
5 |
6 | @Stable
7 | sealed interface TextResource {
8 |
9 | @Stable
10 | class ResourceString(internal val stringId: Int, internal vararg val params: Any) : TextResource
11 |
12 | @Stable
13 | @JvmInline
14 | value class NormalString(internal val text: String) : TextResource
15 |
16 | companion object {
17 | fun TextResource?.asString(context: Context): String {
18 | return when (this) {
19 | null -> ""
20 | is ResourceString -> context.resources.getString(stringId, *params)
21 | is NormalString -> text
22 | }
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/datetime/DateUtils.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.datetime
2 |
3 | import kotlinx.collections.immutable.ImmutableList
4 | import pseudoankit.droid.core.model.TaskyDate
5 | import pseudoankit.droid.core.util.extension.mapToImmutableList
6 | import java.time.LocalDate
7 | import java.time.Month
8 |
9 | object DateUtils {
10 | private val dateRangeOfMonthCache = mutableMapOf>()
11 |
12 | /**
13 | * @return[List] list of days which falls in the same month passed in the param
14 | * @param[date] date for which range needed
15 | */
16 | fun getDateRangeForMonth(date: TaskyDate): ImmutableList {
17 | return dateRangeOfMonthCache.getOrPut(date.value.month) {
18 | (1..date.value.lengthOfMonth()).map {
19 | LocalDate.of(date.value.year, date.value.month, it)
20 | }
21 | }.mapToImmutableList {
22 | // wrapping with TaskyDate and mapping the year same passed by client
23 | TaskyDate(
24 | value = LocalDate.of(date.value.year, it.month, it.dayOfMonth)
25 | )
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/datetime/TimeUtils.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.datetime
2 |
3 | import pseudoankit.droid.core.model.TaskyTime
4 | import java.time.LocalTime
5 |
6 | object TimeUtils {
7 | val TaskyTime?.orNow get() = this ?: TaskyTime(LocalTime.now())
8 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/extension/CollectionExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.extension
2 |
3 | import kotlinx.collections.immutable.ImmutableList
4 | import kotlinx.collections.immutable.toImmutableList
5 |
6 | inline fun Iterable.mapToImmutableList(transform: (T) -> R): ImmutableList {
7 | return map(transform).toImmutableList()
8 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/extension/DateTimeExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.extension
2 |
3 | import pseudoankit.droid.core.model.TaskyDate
4 | import pseudoankit.droid.core.model.TaskyTime
5 | import java.time.LocalDate
6 | import java.time.LocalTime
7 | import java.time.format.DateTimeFormatter
8 |
9 | val LocalDate?.orToday: LocalDate get() = this ?: LocalDate.now()
10 |
11 | fun TaskyDate?.parseToString(pattern: String): String? = this?.value.parseToString(pattern)
12 |
13 | fun LocalDate?.parseToString(pattern: String): String? = this?.run {
14 | format(DateTimeFormatter.ofPattern(pattern))
15 | }
16 |
17 | /* -------------------------------------- -------------------------------------- */
18 |
19 | val LocalTime?.orNow: LocalTime get() = this ?: LocalTime.now()
20 |
21 | fun TaskyTime?.parseToString(pattern: String = "hh:mm a"): String? =
22 | this?.value.parseToString(pattern)
23 |
24 | fun LocalTime?.parseToString(pattern: String = "hh:mm a"): String? = this?.run {
25 | format(DateTimeFormatter.ofPattern(pattern))
26 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/extension/IntentExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.extension
2 |
3 | import android.os.Build
4 | import android.os.Bundle
5 | import android.os.Parcelable
6 |
7 | inline fun Bundle.getParcelableData(key: String): T? {
8 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
9 | getParcelable(key, T::class.java)
10 | } else {
11 | getParcelable(key)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/extension/LazyInitializerExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.extension
2 |
3 | fun nonThreadSafeLazy(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer)
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/extension/PrimitiveExtension.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.extension
2 |
3 | val Boolean?.orFalse get() = this ?: false
4 | val Boolean?.orTrue get() = this ?: true
5 |
6 | val Int?.orZero get() = this ?: 0
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/extension/SafeCall.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.extension
2 |
3 | import pseudoankit.droid.core.R
4 | import pseudoankit.droid.core.logger.TaskyLogger
5 | import pseudoankit.droid.core.util.TaskyResult
6 | import pseudoankit.droid.core.util.TextResource
7 | import java.net.SocketTimeoutException
8 |
9 | /**
10 | * helper function to execute a code block and handle exception
11 | * @param[block] execute your code
12 | * @param[onError] executed when any exception occurs with [TextResource] as error message
13 | * @return[T] type of result to be executed
14 | */
15 | inline fun safeCall(block: () -> T, onError: (TextResource) -> T): T = try {
16 | block()
17 | } catch (exception: Exception) {
18 | TaskyLogger.info("SafeCall exception", exception.message.orEmpty())
19 | val errorMessage = when (exception) {
20 | is SocketTimeoutException -> TextResource.ResourceString(R.string.error_no_internet)
21 | else -> {
22 | when {
23 | exception.message.isNullOrBlank().not() ->
24 | TextResource.NormalString(exception.message.orEmpty())
25 | else -> TextResource.ResourceString(R.string.something_went_wrong)
26 | }
27 | }
28 | }
29 | onError(errorMessage)
30 | }
31 |
32 | /**
33 | * helper function to execute a code block and handle exception
34 | * @param[block] execute your code
35 | * @return[TextResource]
36 | */
37 | inline fun safeCall(block: () -> TaskyResult): TaskyResult = safeCall(
38 | block = block,
39 | onError = {
40 | TaskyResult.Error(it)
41 | }
42 | )
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/util/validator/Validator.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.util.validator
2 |
3 | import pseudoankit.droid.core.util.TextResource
4 |
5 | object Validator {
6 | private const val EMAIL_PATTERN = "^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}\$"
7 |
8 | fun validateName(name: String?): TextResource? {
9 | if (name.isNullOrBlank()) return TextResource.NormalString("Name cannot be empty")
10 | return null
11 | }
12 |
13 | /**
14 | * email validator returns null if [email] is valid else will return error message
15 | */
16 | fun validateEmail(email: String?): TextResource? {
17 | if (email.isNullOrBlank()) return TextResource.NormalString("Email cannot be empty")
18 |
19 | val isValid = email.matches(Regex(pattern = EMAIL_PATTERN))
20 | if (isValid.not()) return TextResource.NormalString("You have entered an invalid email address")
21 |
22 | return null
23 | }
24 |
25 | /**
26 | * password validator returns null if email is valid else will return error message
27 | */
28 | fun validatePassword(
29 | password: String?,
30 | emptyErrorMessage: TextResource = TextResource.NormalString("Password cannot be empty"),
31 | invalidLengthErrorMessage: TextResource = TextResource.NormalString("Password cannot be less than 6 digits"),
32 | ): TextResource? {
33 | if (password.isNullOrBlank()) return emptyErrorMessage
34 | if (password.contains(' ')) return TextResource.NormalString("Password cannot contain whitespaces")
35 | if (password.length < 6) return invalidLengthErrorMessage
36 |
37 | return null
38 | }
39 | }
--------------------------------------------------------------------------------
/core/core/src/main/java/pseudoankit/droid/core/widget/UpdateAppWidgetFlow.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.core.widget
2 |
3 | import kotlinx.coroutines.flow.MutableSharedFlow
4 | import org.koin.core.qualifier.named
5 | import org.koin.core.scope.Scope
6 |
7 | /*
8 | * flow to update app widgets in home screen
9 | */
10 |
11 | typealias UpdateAppWidgetFlow = MutableSharedFlow
12 |
13 | val UpdateAppWidgetFlowNamed = named("widget")
14 |
15 | val Scope.UpdateAppWidgetFlowInstance get() = get(UpdateAppWidgetFlowNamed)
--------------------------------------------------------------------------------
/core/core/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | No Internet
4 | Something went wrong
5 |
--------------------------------------------------------------------------------
/core/database-manager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/database-manager/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.AndroidLibrary)
3 | id(Plugins.Core)
4 | id(Plugins.RoomDatabase)
5 | }
6 |
7 | android {
8 | namespace = "pseudoankit.droid.dbmanager"
9 | }
10 |
11 | dependencies {
12 | implementation(projects.core.agendaManger)
13 | implementation(projects.core.core)
14 | }
--------------------------------------------------------------------------------
/core/database-manager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/database-manager/src/main/java/pseudoankit/droid/dbmanager/TaskyDataBase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.dbmanager
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 | import androidx.room.TypeConverters
8 | import pseudoankit.droid.agendamanger.data.local.dao.ReminderDao
9 | import pseudoankit.droid.agendamanger.data.local.entity.ReminderEntity
10 | import pseudoankit.droid.dbmanager.typeconvertor.DateTimeTypeConvertor
11 |
12 | @Database(
13 | entities = [ReminderEntity::class],
14 | version = 1
15 | )
16 | @TypeConverters(
17 | DateTimeTypeConvertor::class
18 | )
19 | abstract class TaskyDataBase : RoomDatabase() {
20 |
21 | abstract val reminderDao: ReminderDao
22 |
23 | companion object {
24 |
25 | @Volatile
26 | private var instance: TaskyDataBase? = null
27 | private val LOCK = Any()
28 |
29 | operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
30 | instance ?: buildDatabase(context).also {
31 | instance = it
32 | }
33 | }
34 |
35 | private fun buildDatabase(context: Context) =
36 | Room.databaseBuilder(
37 | context.applicationContext,
38 | TaskyDataBase::class.java,
39 | "tasky_manager"
40 | ).build()
41 | }
42 | }
--------------------------------------------------------------------------------
/core/database-manager/src/main/java/pseudoankit/droid/dbmanager/di/DataBaseModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.dbmanager.di
2 |
3 | import org.koin.android.ext.koin.androidApplication
4 | import org.koin.dsl.module
5 | import pseudoankit.droid.dbmanager.TaskyDataBase
6 |
7 | object DataBaseModule {
8 |
9 | operator fun invoke() = module {
10 | single { TaskyDataBase(androidApplication()) }
11 |
12 | factory { get().reminderDao }
13 | }
14 | }
--------------------------------------------------------------------------------
/core/database-manager/src/main/java/pseudoankit/droid/dbmanager/typeconvertor/DateTimeTypeConvertor.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.dbmanager.typeconvertor
2 |
3 | import androidx.room.TypeConverter
4 | import kotlinx.serialization.decodeFromString
5 | import kotlinx.serialization.encodeToString
6 | import pseudoankit.droid.core.util.defaultJsonSerializer
7 | import java.time.LocalDate
8 | import java.time.LocalTime
9 |
10 | class DateTimeTypeConvertor {
11 |
12 | @kotlinx.serialization.Serializable
13 | private data class ParseDate(val year: Int, val month: Int, val day: Int)
14 |
15 | @kotlinx.serialization.Serializable
16 | private data class ParseTime(val hour: Int, val minute: Int)
17 |
18 |
19 | @TypeConverter
20 | fun encode(date: LocalDate): String {
21 | return defaultJsonSerializer.encodeToString(
22 | ParseDate(
23 | date.year,
24 | date.month.value,
25 | date.dayOfMonth
26 | )
27 | )
28 | }
29 |
30 | @TypeConverter
31 | fun decodeToDate(date: String): LocalDate {
32 | val parsed = defaultJsonSerializer.decodeFromString(date)
33 | return LocalDate.of(parsed.year, parsed.month, parsed.day)
34 | }
35 |
36 | @TypeConverter
37 | fun encode(time: LocalTime): String {
38 | return defaultJsonSerializer.encodeToString(ParseTime(time.hour, time.minute))
39 | }
40 |
41 | @TypeConverter
42 | fun decodeToTime(time: String): LocalTime {
43 | val parse = defaultJsonSerializer.decodeFromString(time)
44 | return LocalTime.of(parse.hour, parse.minute)
45 | }
46 | }
--------------------------------------------------------------------------------
/core/design-system/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/design-system/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.AndroidLibrary)
3 | id(Plugins.Core)
4 | id(Plugins.ComposeCore)
5 | }
6 |
7 | android {
8 | namespace = "pseudoankit.droid.unify"
9 | }
10 |
11 | dependencies {
12 | implementation(libs.compose.fontawesome)
13 | implementation(libs.compose.datepicker)
14 | implementation(libs.compose.placeholder)
15 | implementation(libs.compose.swipe)
16 | }
17 |
--------------------------------------------------------------------------------
/core/design-system/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core/design-system/src/debug/java/pseudoankit/droid/unify/UnifyActivity.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import androidx.activity.ComponentActivity
7 | import androidx.activity.compose.setContent
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.lazy.LazyColumn
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 |
14 | fun Context.navigateToUnifyDemo() {
15 | Intent(this, UnifyActivity::class.java).apply {
16 | startActivity(this)
17 | }
18 | }
19 |
20 | class UnifyActivity : ComponentActivity() {
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContent {
25 |
26 | LazyColumn(
27 | modifier = Modifier.fillMaxSize(),
28 | verticalArrangement = Arrangement.spacedBy(16.dp)
29 | ) {
30 | item {
31 | }
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/button/UnifyButtonMapper.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.button
2 |
3 | import androidx.compose.material3.ButtonDefaults
4 | import androidx.compose.runtime.Composable
5 | import pseudoankit.droid.unify.token.UnifyColors
6 |
7 | internal object UnifyButtonMapper {
8 |
9 | val UnifyButtonConfig.State.isDisabled get() = this == UnifyButtonConfig.State.Disabled
10 | val UnifyButtonConfig.State.isLoading get() = this == UnifyButtonConfig.State.Loading
11 | val UnifyButtonConfig.State.isEnabled get() = this == UnifyButtonConfig.State.Enabled
12 |
13 | @Composable
14 | fun UnifyButtonConfig.State.buttonColors() = when (this) {
15 | UnifyButtonConfig.State.Loading -> ButtonDefaults.buttonColors(
16 | containerColor = UnifyColors.Black,
17 | contentColor = UnifyColors.White
18 | )
19 | UnifyButtonConfig.State.Enabled -> ButtonDefaults.buttonColors(
20 | containerColor = UnifyColors.Black,
21 | contentColor = UnifyColors.White
22 | )
23 | UnifyButtonConfig.State.Disabled -> ButtonDefaults.buttonColors(
24 | containerColor = UnifyColors.Gray400,
25 | contentColor = UnifyColors.Gray800
26 | )
27 | }
28 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/card/UnifyCard.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.card
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.foundation.shape.RoundedCornerShape
5 | import androidx.compose.material3.Surface
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.unit.Dp
10 | import androidx.compose.ui.unit.dp
11 | import pseudoankit.droid.unify.token.UnifyColors
12 | import pseudoankit.droid.unify.token.UnifyDimens
13 |
14 | data class UnifyCardConfig(
15 | val radius: Dp = UnifyDimens.Radius.Medium,
16 | val color: Color = UnifyColors.White,
17 | val modifier: Modifier = Modifier,
18 | val elevation: Dp = 2.dp
19 | )
20 |
21 | @Composable
22 | fun UnifyCard(config: UnifyCardConfig = UnifyCardConfig(), content: @Composable () -> Unit) {
23 | Surface(
24 | modifier = config.modifier.then(Modifier.padding(UnifyDimens.Dp_1)),
25 | shape = RoundedCornerShape(config.radius),
26 | color = config.color,
27 | content = content,
28 | shadowElevation = config.elevation,
29 | tonalElevation = config.elevation,
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/dialog/UnifyDialog.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.dialog
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.ui.text.TextStyle
7 | import com.vanpra.composematerialdialogs.MaterialDialog
8 | import com.vanpra.composematerialdialogs.MaterialDialogScope
9 | import com.vanpra.composematerialdialogs.rememberMaterialDialogState
10 | import pseudoankit.droid.unify.token.UnifyColors
11 | import pseudoankit.droid.unify.token.UnifyDimens
12 |
13 | @Composable
14 | fun UnifyDialog(
15 | state: UnifyDialogState,
16 | showActionButton: Boolean = true,
17 | content: @Composable MaterialDialogScope.() -> Unit
18 | ) {
19 | val materialPickerState = rememberMaterialDialogState()
20 | when (state.showing) {
21 | true -> materialPickerState.show()
22 | false -> materialPickerState.hide()
23 | }
24 |
25 | val onActionButtonClick = remember {
26 | {
27 | state.hide()
28 | }
29 | }
30 | val actionButtonTextStyle = remember {
31 | TextStyle.Default.copy(color = UnifyColors.Black)
32 | }
33 |
34 | MaterialDialog(
35 | dialogState = materialPickerState,
36 | buttons = {
37 | if (showActionButton.not()) return@MaterialDialog
38 | positiveButton(
39 | "OK",
40 | onClick = onActionButtonClick,
41 | textStyle = actionButtonTextStyle
42 | )
43 | negativeButton(
44 | "Cancel",
45 | onClick = onActionButtonClick,
46 | textStyle = actionButtonTextStyle
47 | )
48 | },
49 | shape = RoundedCornerShape(UnifyDimens.Radius.Large),
50 | autoDismiss = true,
51 | onCloseRequest = {
52 | state.hide()
53 | },
54 | content = content
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/dialog/UnifyDialogState.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.dialog
2 |
3 | import androidx.compose.runtime.*
4 |
5 | @Composable
6 | fun rememberUnifyDialogState(initialValue: Boolean = false): UnifyDialogState {
7 | return remember {
8 | UnifyDialogState(initialValue = initialValue)
9 | }
10 | }
11 |
12 | class UnifyDialogState internal constructor(initialValue: Boolean = false) {
13 | internal var showing by mutableStateOf(initialValue)
14 |
15 | fun show() {
16 | showing = true
17 | }
18 |
19 | fun hide() {
20 | showing = false
21 | }
22 |
23 | fun toggle() {
24 | showing = showing.not()
25 | }
26 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/dialog/datepicker/UnifyDatePicker.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.dialog.datepicker
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Immutable
5 | import com.vanpra.composematerialdialogs.datetime.date.datepicker
6 | import pseudoankit.droid.unify.component.dialog.UnifyDialog
7 | import pseudoankit.droid.unify.component.dialog.UnifyDialogState
8 | import pseudoankit.droid.unify.component.dialog.rememberUnifyDialogState
9 | import java.time.LocalDate
10 |
11 | /**
12 | * create date picker dialog
13 | * @param[initialDate] date to be selected when picker is opened initially
14 | */
15 | @Immutable
16 | data class UnifyDatePickerConfig(
17 | val initialDate: LocalDate = LocalDate.now()
18 | )
19 |
20 | /**
21 | * create date picker dialog
22 | * @param[datePickerState] pass the state of date picker, create state instance by [rememberUnifyDialogState()]
23 | * @param[onDateChanged] callback when positive button is clicked
24 | * @param config configs for date picker
25 | */
26 | @Composable
27 | fun UnifyDatePicker(
28 | config: UnifyDatePickerConfig,
29 | datePickerState: UnifyDialogState,
30 | onDateChanged: (LocalDate) -> Unit,
31 | ) = with(config) {
32 | UnifyDialog(state = datePickerState) {
33 | datepicker(
34 | initialDate = initialDate,
35 | onDateChange = onDateChanged,
36 | colors = UnifyDatePickerToken.colors
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/dialog/datepicker/UnifyDatePickerToken.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.dialog.datepicker
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.compose.ui.graphics.Color
6 | import com.vanpra.composematerialdialogs.datetime.date.DatePickerColors
7 | import pseudoankit.droid.unify.token.UnifyColors
8 | import pseudoankit.droid.unify.utils.internal.rememberMutableState
9 |
10 | internal object UnifyDatePickerToken {
11 |
12 | val colors = object : DatePickerColors {
13 | override val calendarHeaderTextColor: Color
14 | get() = UnifyColors.Black
15 | override val headerBackgroundColor: Color
16 | get() = UnifyColors.Black
17 | override val headerTextColor: Color
18 | get() = UnifyColors.White
19 |
20 | @Composable
21 | override fun dateBackgroundColor(active: Boolean): State {
22 | return rememberMutableState(
23 | value = if (active) UnifyColors.Black else UnifyColors.White,
24 | active,
25 | )
26 | }
27 |
28 | @Composable
29 | override fun dateTextColor(active: Boolean): State {
30 | return rememberMutableState(
31 | value = if (active) UnifyColors.White else UnifyColors.Black,
32 | active,
33 | )
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/dialog/timepicker/UnifyTimePicker.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.dialog.timepicker
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Stable
5 | import com.vanpra.composematerialdialogs.datetime.time.timepicker
6 | import pseudoankit.droid.unify.component.dialog.UnifyDialog
7 | import pseudoankit.droid.unify.component.dialog.UnifyDialogState
8 | import java.time.LocalTime
9 |
10 | /**
11 | * create date picker dialog
12 | * @param[timePickerState] pass the state of date picker, create state instance by [rememberUnifyDialogState()]
13 | * @param[config] config param for time picker
14 | * @param[onTimeChanged] callback when positive button is clicked
15 | */
16 |
17 | @Composable
18 | fun UnifyTimePicker(
19 | config: UnifyTimePickerConfig,
20 | timePickerState: UnifyDialogState,
21 | onTimeChanged: (LocalTime) -> Unit
22 | ) = with(config) {
23 | UnifyDialog(
24 | state = timePickerState
25 | ) {
26 | timepicker(
27 | initialTime = initialTime,
28 | onTimeChange = onTimeChanged,
29 | colors = UnifyTimePickerToken.colors
30 | )
31 | }
32 | }
33 |
34 | /**
35 | * @param[initialTime] date to be selected when picker is opened initially
36 | */
37 | @Stable
38 | data class UnifyTimePickerConfig(
39 | val initialTime: LocalTime = LocalTime.now()
40 | )
41 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/divider/UnifyDivider.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.divider
2 |
3 | import androidx.compose.material3.Divider
4 | import androidx.compose.material3.DividerDefaults
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.unit.Dp
9 | import androidx.compose.ui.unit.dp
10 |
11 | @Composable
12 | fun UnifyDivider(
13 | modifier: Modifier = Modifier,
14 | color: Color = DividerDefaults.color,
15 | height: Dp = DividerDefaults.Thickness,
16 | ) {
17 | Divider(modifier = modifier, color = color, thickness = height)
18 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/fab/UnifyFloatingButton.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.fab
2 |
3 | import androidx.compose.foundation.shape.CircleShape
4 | import androidx.compose.material3.FloatingActionButton
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import pseudoankit.droid.unify.component.icon.UnifyIcon
9 | import pseudoankit.droid.unify.component.icon.UnifyIconConfig
10 | import pseudoankit.droid.unify.token.UnifyColors
11 |
12 | @Composable
13 | fun UnifyFloatingButton(
14 | iconConfig: UnifyIconConfig,
15 | modifier: Modifier = Modifier,
16 | onClick: () -> Unit,
17 | ) {
18 | val updatedIconConfig = remember(iconConfig) {
19 | iconConfig.copy(tint = UnifyColors.White)
20 | }
21 |
22 | FloatingActionButton(
23 | onClick = onClick,
24 | shape = CircleShape,
25 | containerColor = UnifyColors.Black,
26 | modifier = modifier
27 | ) {
28 | UnifyIcon(config = updatedIconConfig)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/icon/UnifyIcon.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.icon
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.IconButton
6 | import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.CompositionLocalProvider
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.Dp
13 | import pseudoankit.droid.unify.token.UnifyDimens
14 |
15 | /**
16 | * Icon composable to display icon
17 | * @param config configurations for icon
18 | */
19 | @Composable
20 | fun UnifyIcon(config: UnifyIconConfig?) {
21 | when {
22 | config == null -> return
23 | config.onClick != null -> CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides config.provideTouchTargetSpacing) {
24 | IconButton(onClick = config.onClick, modifier = config.modifier) {
25 | UnifyIconInternal(config = config)
26 | }
27 | }
28 | else -> Box(modifier = config.modifier, contentAlignment = Alignment.Center) {
29 | UnifyIconInternal(config = config)
30 | }
31 | }
32 | }
33 |
34 | /**
35 | * @param[icon] icon to show
36 | * @param[size] size of icon
37 | * @param[onClick] consuming onClick will use icon button to provide ripple effect
38 | * @param[provideTouchTargetSpacing] boolean to change extra padding when using icon button
39 | * @param modifier modifier of outer box in which icon is wrapped
40 | */
41 | data class UnifyIconConfig(
42 | val icon: UnifyIcons,
43 | val modifier: Modifier = Modifier,
44 | val contentDescription: String = "",
45 | val tint: Color = Color.Black,
46 | val size: Dp = UnifyDimens.Dp_24,
47 | val onClick: (() -> Unit)? = null,
48 | val provideTouchTargetSpacing: Boolean = false
49 | )
50 |
51 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/icon/UnifyIconInternal.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.icon
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.material3.Icon
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.res.painterResource
8 |
9 | @Composable
10 | fun UnifyIconInternal(config: UnifyIconConfig) {
11 | when (config.icon.iconType) {
12 | is UnifyIcons.IconType.FontAwesome -> FontAwesomeIcon(config = config)
13 | is UnifyIcons.IconType.Vector -> VectorIcon(config = config)
14 | is UnifyIcons.IconType.Svg -> SvgIcon(config = config)
15 | }
16 | }
17 |
18 | @Composable
19 | private fun SvgIcon(config: UnifyIconConfig) {
20 | val icon = (config.icon.iconType as? UnifyIcons.IconType.Svg)?.drawableRes ?: return
21 | Icon(
22 | painter = painterResource(id = icon),
23 | contentDescription = "",
24 | modifier = Modifier.size(config.size),
25 | tint = config.tint
26 | )
27 | }
28 |
29 | @Composable
30 | private fun FontAwesomeIcon(config: UnifyIconConfig) {
31 | val faIcon = (config.icon.iconType as? UnifyIcons.IconType.FontAwesome)?.faIcon ?: return
32 |
33 | lazycoder21.droid.compose.FontAwesomeIcon(
34 | faIcon = faIcon,
35 | size = config.size,
36 | tint = config.tint
37 | )
38 | }
39 |
40 | @Composable
41 | private fun VectorIcon(config: UnifyIconConfig) {
42 | val imageVector =
43 | (config.icon.iconType as? UnifyIcons.IconType.Vector)?.imageVector ?: return
44 | Icon(
45 | imageVector = imageVector,
46 | contentDescription = config.contentDescription,
47 | modifier = Modifier.size(config.size),
48 | tint = config.tint
49 | )
50 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/list/UnifyListItem.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.list
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import pseudoankit.droid.unify.component.icon.UnifyIcons
10 | import pseudoankit.droid.unify.component.switch.UnifySwitchConfig
11 | import pseudoankit.droid.unify.token.UnifyColors
12 |
13 | @Composable
14 | fun UnifyListItem(config: UnifyListItemConfig) {
15 | Row(
16 | modifier = config.modifier,
17 | verticalAlignment = Alignment.CenterVertically
18 | ) {
19 | UnifyListComponents.LeadingIcon(
20 | leadingIcon = config.leadingIcon,
21 | tint = config.color
22 | )
23 |
24 | UnifyListComponents.Label(
25 | label = config.label,
26 | color = config.color,
27 | modifier = Modifier.weight(1f)
28 | )
29 |
30 | UnifyListComponents.TrailingIcon(
31 | trailingIcon = config.trailingSection,
32 | color = config.color
33 | )
34 | }
35 | }
36 |
37 | data class UnifyListItemConfig(
38 | val leadingIcon: UnifyIcons? = null,
39 | val label: String,
40 | val trailingSection: TrailingSection? = null,
41 | val modifier: Modifier = Modifier,
42 | val color: Color = UnifyColors.Black
43 | ) {
44 |
45 | sealed interface TrailingSection {
46 | @JvmInline
47 | value class Switch(val value: UnifySwitchConfig) : TrailingSection
48 |
49 | @JvmInline
50 | value class NoAction(val value: UnifyIcons) : TrailingSection
51 | }
52 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/pill/UnifyPill.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.pill
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.draw.clip
11 | import androidx.compose.ui.unit.dp
12 | import pseudoankit.droid.unify.component.textview.UnifyTextView
13 | import pseudoankit.droid.unify.component.textview.UnifyTextViewConfig
14 | import pseudoankit.droid.unify.token.UnifyColors
15 | import pseudoankit.droid.unify.token.UnifyDimens
16 | import pseudoankit.droid.unify.utils.clickable
17 |
18 | data class UnifyPillConfig(
19 | val label: String,
20 | val modifier: Modifier = Modifier,
21 | )
22 |
23 | @Composable
24 | fun UnifyPill(
25 | config: UnifyPillConfig,
26 | onClick: (() -> Unit)? = null
27 | ) {
28 | Row(
29 | modifier = config.modifier
30 | .clip(RoundedCornerShape(UnifyDimens.Radius.Large))
31 | .background(color = UnifyColors.Blue400)
32 | .clickable(onClick = onClick)
33 | .padding(horizontal = 8.dp, vertical = 6.dp),
34 | verticalAlignment = Alignment.CenterVertically
35 | ) {
36 | UnifyTextView(config = UnifyTextViewConfig(text = config.label, color = UnifyColors.White))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/progressbar/UnifyProgressIndicator.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.progressbar
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.material3.CircularProgressIndicator
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.unit.Dp
9 | import pseudoankit.droid.unify.token.UnifyColors
10 | import pseudoankit.droid.unify.token.UnifyDimens
11 |
12 | @Composable
13 | fun UnifyProgressIndicator(
14 | config: UnifyProgressIndicatorConfig = UnifyProgressIndicatorConfig()
15 | ) {
16 | when (config.type) {
17 | UnifyProgressIndicatorConfig.Type.Circular -> CircularProgressIndicator(
18 | modifier = config.modifier,
19 | color = config.color,
20 | strokeWidth = config.strokeWidth
21 | )
22 | }
23 | }
24 |
25 | data class UnifyProgressIndicatorConfig(
26 | val type: Type = Type.Circular,
27 | val modifier: Modifier = Modifier,
28 | val color: Color = UnifyColors.Black,
29 | val strokeWidth: Dp = UnifyDimens.Dp_4
30 | ) {
31 | enum class Type { Circular }
32 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/switch/UnifySwitch.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.switch
2 |
3 | import androidx.compose.material3.Switch
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 |
7 | data class UnifySwitchConfig(
8 | val checked: Boolean,
9 | val onCheckedChange: (() -> Unit)?,
10 | val modifier: Modifier = Modifier,
11 | )
12 |
13 | @Composable
14 | fun UnifySwitch(config: UnifySwitchConfig) = with(config) {
15 | Switch(
16 | checked = checked,
17 | onCheckedChange = {
18 | onCheckedChange?.invoke()
19 | },
20 | enabled = onCheckedChange != null,
21 | colors = UnifySwitchTokens.colors()
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/switch/UnifySwitchTokens.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.switch
2 |
3 | import androidx.compose.material3.SwitchColors
4 | import androidx.compose.material3.SwitchDefaults
5 | import androidx.compose.runtime.Composable
6 | import pseudoankit.droid.unify.token.UnifyColors
7 |
8 | internal object UnifySwitchTokens {
9 |
10 | private val BorderColor = UnifyColors.Transparent
11 |
12 | @Composable
13 | fun colors(): SwitchColors {
14 | return SwitchDefaults.colors(
15 | checkedThumbColor = UnifyColors.Black,
16 | uncheckedThumbColor = UnifyColors.Black,
17 | disabledCheckedThumbColor = UnifyColors.Gray100,
18 | disabledUncheckedThumbColor = UnifyColors.Gray100,
19 | checkedTrackColor = UnifyColors.Black.copy(alpha = .1f),
20 | uncheckedTrackColor = UnifyColors.Gray100.copy(alpha = .9f),
21 | // disabledCheckedTrackColor =,
22 | // disabledUncheckedTrackColor =,
23 | // checkedIconColor = ,
24 | // uncheckedIconColor =,
25 | // disabledCheckedIconColor =,
26 | // disabledUncheckedIconColor =,
27 | checkedBorderColor = BorderColor,
28 | uncheckedBorderColor = BorderColor,
29 | disabledCheckedBorderColor = BorderColor,
30 | disabledUncheckedBorderColor = BorderColor,
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/textfield/UnifyTextFieldDefaults.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.textfield
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.ui.focus.FocusRequester
6 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController
7 | import kotlinx.coroutines.delay
8 | import pseudoankit.droid.unify.component.textview.UnifyTextType
9 | import pseudoankit.droid.unify.component.textview.UnifyTextView
10 | import pseudoankit.droid.unify.component.textview.UnifyTextViewConfig
11 | import pseudoankit.droid.unify.token.UnifyColors
12 |
13 | object UnifyTextFieldDefaults {
14 |
15 | fun placeHolder(
16 | value: String,
17 | textType: UnifyTextType = UnifyTextType.BodyLarge
18 | ) = UnifyTextViewConfig(
19 | text = value,
20 | textType = textType,
21 | color = UnifyColors.Gray800
22 | )
23 |
24 | /**
25 | * Utility method to request focus and open keyboard
26 | */
27 | @Composable
28 | fun ManageFocus(focusState: UnifyTextFieldConfig.FocusState, focusRequester: FocusRequester) {
29 | val keyboard = LocalSoftwareKeyboardController.current
30 |
31 | LaunchedEffect(focusState) {
32 | when (focusState) {
33 | UnifyTextFieldConfig.FocusState.Request -> {
34 | focusRequester.requestFocus()
35 | delay(100)
36 | keyboard?.show()
37 | }
38 | UnifyTextFieldConfig.FocusState.Capture -> focusRequester.captureFocus()
39 | UnifyTextFieldConfig.FocusState.Free -> {
40 | focusRequester.freeFocus()
41 | delay(100)
42 | keyboard?.hide()
43 | }
44 | UnifyTextFieldConfig.FocusState.None -> {}
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/textfield/internal/UnifyTextFieldTokens.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.textfield.internal
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import pseudoankit.droid.unify.component.textfield.UnifyTextFieldConfig
5 | import pseudoankit.droid.unify.component.textview.UnifyTextViewConfig
6 | import pseudoankit.droid.unify.token.UnifyColors
7 | import pseudoankit.droid.unify.token.UnifyDimens
8 |
9 | internal object UnifyTextFieldTokens {
10 | object Icon {
11 | val Color = UnifyColors.Gray500
12 | val Size = UnifyDimens.Dp_24
13 | }
14 |
15 | object Background {
16 | fun shape(type: UnifyTextFieldConfig.Type) = when(type) {
17 | UnifyTextFieldConfig.Type.Outlined -> RoundedCornerShape(UnifyDimens.Radius.Small)
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/textview/UnifyTextType.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.textview
2 |
3 | import androidx.compose.ui.text.TextStyle
4 | import androidx.compose.ui.text.font.FontWeight
5 | import androidx.compose.ui.unit.sp
6 |
7 | private val typography by lazy {
8 | androidx.compose.material3.Typography()
9 | }
10 |
11 | enum class UnifyTextType(val textStyle: TextStyle) {
12 | DisplayLarge(typography.displayLarge),
13 | DisplayMedium(typography.displayMedium),
14 | DisplaySmall(typography.displaySmall),
15 | HeadlineLarge(typography.headlineLarge),
16 | HeadlineMedium(typography.headlineMedium),
17 | HeadlineSmall(typography.headlineSmall),
18 | TitleLarge(typography.titleLarge.copy(fontWeight = FontWeight.Bold)),
19 | TitleMedium(typography.titleMedium.copy(fontWeight = FontWeight.Bold)),
20 | TitleSmall(typography.titleSmall.copy(fontWeight = FontWeight.Bold)),
21 | BodyLarge(typography.bodyLarge.copy(fontSize = 18.sp)),
22 | BodyMedium(typography.bodyMedium),
23 | BodySmall(typography.bodySmall),
24 | LabelLarge(typography.labelLarge),
25 | LabelMedium(typography.labelMedium),
26 | LabelSmall(typography.labelSmall)
27 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/textview/UnifyTextView.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.textview
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.text.font.FontStyle
8 | import androidx.compose.ui.text.style.TextAlign
9 | import androidx.compose.ui.text.style.TextDecoration
10 | import androidx.compose.ui.text.style.TextOverflow
11 |
12 | @Composable
13 | fun UnifyTextView(config: UnifyTextViewConfig?) {
14 |
15 | config?.apply {
16 | if (text == null) return
17 |
18 | Text(
19 | text = text,
20 | color = color,
21 | textDecoration = textDecoration,
22 | textAlign = textAlign,
23 | maxLines = maxLines,
24 | fontStyle = fontStyle,
25 | style = textType.textStyle,
26 | modifier = modifier,
27 | overflow = TextOverflow.Ellipsis
28 | )
29 | }
30 | }
31 |
32 | data class UnifyTextViewConfig(
33 | val text: String?,
34 | val textType: UnifyTextType = UnifyTextType.LabelMedium,
35 | val color: Color = Color.Unspecified,
36 | val textDecoration: TextDecoration? = null,
37 | val textAlign: TextAlign? = null,
38 | val maxLines: Int = Int.MAX_VALUE,
39 | val fontStyle: FontStyle? = null,
40 | val modifier: Modifier = Modifier
41 | )
42 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/topbar/UnifyTopBar.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.component.topbar
2 |
3 | import androidx.compose.material.ripple.LocalRippleTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.CompositionLocalProvider
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import pseudoankit.droid.unify.component.icon.UnifyIconConfig
9 | import pseudoankit.droid.unify.component.textview.UnifyTextViewConfig
10 | import pseudoankit.droid.unify.token.UnifyColors
11 | import pseudoankit.droid.unify.utils.DarkThemeRipple
12 |
13 | @Composable
14 | fun UnifyTopBar(config: UnifyTopBarConfig?) {
15 | if (config == null) return
16 |
17 | // changing ripple color for black background
18 | CompositionLocalProvider(LocalRippleTheme provides DarkThemeRipple) {
19 | when (config.type) {
20 | UnifyTopBarConfig.Type.Small -> UnifySmallTopBar(config)
21 | }
22 | }
23 | }
24 |
25 | data class UnifyTopBarConfig(
26 | val leadingIcon: UnifyIconConfig? = null,
27 | val title: String = "",
28 | val type: Type = Type.Small,
29 | val trailingSection: TrailingSection? = null,
30 | val tintColor: Color = UnifyColors.White,
31 | val modifier: Modifier = Modifier
32 | ) {
33 |
34 | data class TrailingSection(
35 | val text: UnifyTextViewConfig? = null,
36 | val icon: UnifyIconConfig? = null,
37 | val modifier: Modifier = Modifier,
38 | val onClick: (() -> Unit)? = null
39 | )
40 |
41 | enum class Type { Small }
42 | }
43 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/component/viewpager/HorizontalViewPager.kt:
--------------------------------------------------------------------------------
1 | //package pseudoankit.droid.unify.component.viewpager
2 | //
3 | //import android.annotation.SuppressLint
4 | //import androidx.compose.foundation.layout.Column
5 | //import androidx.compose.foundation.layout.fillMaxSize
6 | //import androidx.compose.runtime.Composable
7 | //import androidx.compose.ui.Modifier
8 | //import androidx.compose.ui.graphics.Color
9 | //import com.google.accompanist.pager.HorizontalPager
10 | //import com.google.accompanist.pager.rememberPagerState
11 | //import kotlinx.collections.immutable.ImmutableList
12 | //import pseudoankit.droid.unify.token.UnifyColors
13 | //
14 | //data class HorizontalViewPagerConfig(
15 | // val items: ImmutableList- ,
16 | // val unSelectedColor: Color = UnifyColors.Black,
17 | // val selectedColor: Color = UnifyColors.Green800,
18 | // val modifier: Modifier = Modifier.fillMaxSize()
19 | //) {
20 | //
21 | // data class Item(
22 | // val label: String,
23 | // val tag: Tag
24 | // )
25 | //
26 | // interface Tag
27 | //}
28 | //
29 | //@Composable
30 | //fun HorizontalViewPager(
31 | // config: HorizontalViewPagerConfig,
32 | // content: @Composable (position: Int, tag: HorizontalViewPagerConfig.Tag) -> Unit
33 | //) {
34 | // val pagerState = rememberPagerState()
35 | //
36 | // Column(modifier = config.modifier) {
37 | // HorizontalTabsInternal.HorizontalTabs(items = config.items, pagerState = pagerState)
38 | // HorizontalPager(
39 | // count = config.items.size,
40 | // state = pagerState
41 | // ) { currentPage ->
42 | // val item = config.items.getOrNull(currentPage) ?: return@HorizontalPager
43 | // content.invoke(currentPage, item.tag)
44 | // }
45 | // }
46 | //}
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/screen/UnifyScreenConfig.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.screen
2 |
3 | import androidx.compose.foundation.layout.BoxScope
4 | import androidx.compose.runtime.Composable
5 | import pseudoankit.droid.unify.component.topbar.UnifyTopBar
6 | import pseudoankit.droid.unify.component.topbar.UnifyTopBarConfig
7 |
8 | /**
9 | * @param[topBar] for custom topBars
10 | */
11 | data class UnifyScreenConfig(
12 | val topBar: @Composable BoxScope.() -> Unit,
13 | val floatingActionButton: @Composable () -> Unit = {}
14 | ) {
15 |
16 | /**
17 | * @param[topBar] topBar or actionBar configs
18 | */
19 | constructor(
20 | topBar: UnifyTopBarConfig? = null,
21 | floatingActionButton: @Composable () -> Unit = {},
22 | ) : this(
23 | topBar = {
24 | UnifyTopBar(topBar)
25 | },
26 | floatingActionButton = floatingActionButton,
27 | )
28 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/token/UnifyDimens.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.token
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | object UnifyDimens {
6 | val Dp_0 = 0.dp
7 | val Dp_1 = 1.dp
8 | val Dp_2 = 2.dp
9 | val Dp_4 = 4.dp
10 | val Dp_6 = 6.dp
11 | val Dp_8 = 8.dp
12 | val Dp_10 = 10.dp
13 | val Dp_12 = 12.dp
14 | val Dp_14 = 14.dp
15 | val Dp_16 = 16.dp
16 | val Dp_20 = 20.dp
17 | val Dp_24 = 24.dp
18 | val Dp_30 = 30.dp
19 | val Dp_32 = 32.dp
20 | val Dp_34 = 34.dp
21 | val Dp_36 = 36.dp
22 | val Dp_44 = 44.dp
23 | val Dp_48 = 48.dp
24 | val Dp_50 = 50.dp
25 | val Dp_52 = 52.dp
26 | val Dp_54 = 54.dp
27 | val Dp_58 = 58.dp
28 | val Dp_60 = 60.dp
29 | val Dp_64 = 64.dp
30 | val Dp_70 = 70.dp
31 |
32 | val ScreenPadding = Dp_16
33 |
34 | object Radius {
35 | val Large = Dp_32
36 | val Medium = Dp_16
37 | val Small = Dp_12
38 | val XSmall = Dp_8
39 | }
40 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/token/UnifyTheme.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.token
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.SideEffect
9 | import androidx.compose.ui.graphics.toArgb
10 | import androidx.compose.ui.platform.LocalContext
11 | import androidx.compose.ui.platform.LocalView
12 | import androidx.core.view.ViewCompat
13 |
14 | private val DarkColorScheme = darkColorScheme(
15 | )
16 |
17 | private val LightColorScheme = lightColorScheme(
18 | )
19 |
20 | @Composable
21 | fun UnifyTheme(
22 | darkTheme: Boolean = isSystemInDarkTheme(),
23 | // Dynamic color is available on Android 12+
24 | dynamicColor: Boolean = true,
25 | content: @Composable () -> Unit
26 | ) {
27 | val colorScheme = when {
28 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
29 | val context = LocalContext.current
30 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
31 | }
32 | darkTheme -> DarkColorScheme
33 | else -> LightColorScheme
34 | }
35 | val view = LocalView.current
36 | if (!view.isInEditMode) {
37 | SideEffect {
38 | (view.context as? Activity)?.window?.statusBarColor = colorScheme.primary.toArgb()
39 | ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
40 | }
41 | }
42 |
43 | MaterialTheme(
44 | colorScheme = colorScheme,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/token/UnifyTokens.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.token
2 |
3 | import pseudoankit.droid.unify.component.textview.UnifyTextType
4 |
5 | internal object UnifyTokens {
6 | object Button {
7 | val Radius = UnifyDimens.Radius.Large
8 | val Height = UnifyDimens.Dp_58
9 | val TextType = UnifyTextType.TitleMedium
10 | }
11 |
12 | object TimePicker {
13 | val SelectedColor = UnifyColors.Blue200
14 | }
15 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/utils/DarkThemeRipple.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.utils
2 |
3 | import androidx.compose.material.ripple.RippleAlpha
4 | import androidx.compose.material.ripple.RippleTheme
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.graphics.Color
7 |
8 | internal object DarkThemeRipple : RippleTheme {
9 |
10 | @Composable
11 | override fun defaultColor() =
12 | RippleTheme.defaultRippleColor(Color.White, lightTheme = true)
13 |
14 | @Composable
15 | override fun rippleAlpha(): RippleAlpha =
16 | RippleTheme.defaultRippleAlpha(Color.Black, lightTheme = true)
17 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/utils/Logs.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.utils
2 |
3 | internal fun logs(value: String) {
4 | println("Design system logs $value")
5 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/utils/Utils.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.utils
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import androidx.compose.ui.focus.FocusRequester
6 | import pseudoankit.droid.unify.R
7 |
8 | typealias UnifyDrawable = R.drawable
9 |
10 | @Composable
11 | fun rememberFocusRequester() = remember {
12 | FocusRequester()
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/core/design-system/src/main/java/pseudoankit/droid/unify/utils/internal/Utils.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.unify.utils.internal
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.remember
6 |
7 | @Composable
8 | internal fun rememberMutableState(value: T, vararg keys: Any?) = remember(*keys) {
9 | mutableStateOf(value)
10 | }
--------------------------------------------------------------------------------
/core/design-system/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core/design-system/src/main/res/drawable/ic_calendar.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/core/design-system/src/main/res/drawable/ic_notification.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/core/design-system/src/main/res/drawable/ic_task.xml:
--------------------------------------------------------------------------------
1 |
6 |
13 |
14 |
--------------------------------------------------------------------------------
/core/design-system/src/main/res/drawable/img_placeholder.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/core/notification-manager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/notification-manager/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | }
4 |
5 | android {
6 | namespace = "pseudoankit.droid.notification_manager"
7 | }
8 |
9 | dependencies {
10 | implementation(projects.core.designSystem)
11 | }
--------------------------------------------------------------------------------
/core/notification-manager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/notification-manager/src/main/java/pseudoankit/droid/notification_manager/TaskyNotifierConfig.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.notification_manager
2 |
3 | import java.util.Random
4 |
5 | data class TaskyNotifierConfig(
6 | val notificationId: Int = Random().nextInt(),
7 | val source: Source,
8 | val title: String = source.name,
9 | val description: String,
10 | val priority: Priority = Priority.Default,
11 | val navigationUrl: String,
12 | ) {
13 |
14 | enum class Priority(
15 | internal val priority: Int,
16 | internal val importance: Int,
17 | ) {
18 | Default(priority = 0, importance = 3),
19 | Low(priority = -1, importance = 2),
20 | Min(priority = -2, importance = 1),
21 | High(priority = 1, importance = 4),
22 | Max(priority = 2, importance = 5)
23 | }
24 |
25 | enum class Source {
26 | Reminder,
27 | Task,
28 | Event
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/notification-manager/src/main/java/pseudoankit/droid/notification_manager/di/NotifierModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.notification_manager.di
2 |
3 | import org.koin.android.ext.koin.androidApplication
4 | import org.koin.dsl.module
5 | import pseudoankit.droid.core.koin.BaseKoinModule
6 | import pseudoankit.droid.notification_manager.TaskyNotifier
7 |
8 | object NotifierModule {
9 |
10 | operator fun invoke() = module {
11 | single { TaskyNotifier(androidApplication()) }
12 | }
13 | }
--------------------------------------------------------------------------------
/core/notification-manager/src/main/java/pseudoankit/droid/notification_manager/util/TaskyNotifierUtils.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.notification_manager.util
2 |
3 | import pseudoankit.droid.notification_manager.TaskyNotifierConfig
4 | import pseudoankit.droid.unify.utils.UnifyDrawable
5 |
6 | internal object TaskyNotifierUtils {
7 |
8 | }
--------------------------------------------------------------------------------
/core/notification-manager/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tasky
4 |
--------------------------------------------------------------------------------
/core/permission-manager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/permission-manager/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | android {
7 | namespace = "com.example.permission_manager"
8 | }
9 |
10 | dependencies {
11 | api("com.google.accompanist:accompanist-permissions:0.26.2-beta")
12 | }
--------------------------------------------------------------------------------
/core/permission-manager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/permission-manager/src/main/java/com/example/permission_manager/PermissionUtils.kt:
--------------------------------------------------------------------------------
1 | package com.example.permission_manager
2 |
3 | import android.content.Context
4 | import androidx.compose.runtime.Composable
5 | import com.google.accompanist.permissions.PermissionState
6 | import com.google.accompanist.permissions.PermissionStatus
7 | import com.google.accompanist.permissions.rememberPermissionState
8 | import com.google.accompanist.permissions.shouldShowRationale
9 | import pseudoankit.droid.coreui.util.extension.navigateToSettings
10 | import pseudoankit.droid.unify.component.button.UnifyButton
11 | import pseudoankit.droid.unify.component.button.UnifyButtonConfig
12 |
13 | fun PermissionState.requestPermission(context: Context) = when (taskyStatus) {
14 | TaskyPermissionStatus.Granted -> { /* no-op */
15 | }
16 | TaskyPermissionStatus.DeclinedOnce -> launchPermissionRequest()
17 | TaskyPermissionStatus.DeclinedPermanently -> context.navigateToSettings()
18 | }
19 |
20 | val PermissionState.isGranted get() = this.status == PermissionStatus.Granted
21 |
22 | val PermissionState.taskyStatus
23 | get() = when (this.status) {
24 | is PermissionStatus.Denied -> {
25 | if (this.status.shouldShowRationale) {
26 | TaskyPermissionStatus.DeclinedOnce
27 | } else {
28 | TaskyPermissionStatus.DeclinedPermanently
29 | }
30 | }
31 | PermissionStatus.Granted -> {
32 | TaskyPermissionStatus.Granted
33 | }
34 | }
35 |
36 | @Composable
37 | internal fun DemoPermission() {
38 | val launcher =
39 | rememberPermissionState(permission = android.Manifest.permission.ACCESS_FINE_LOCATION)
40 |
41 | when (launcher.taskyStatus) {
42 | TaskyPermissionStatus.Granted -> {}
43 | TaskyPermissionStatus.DeclinedOnce -> {}
44 | TaskyPermissionStatus.DeclinedPermanently -> {}
45 | }
46 |
47 | UnifyButton(UnifyButtonConfig(text = "Btn")) {
48 | launcher.launchPermissionRequest()
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/core/permission-manager/src/main/java/com/example/permission_manager/TaskyPermissionStatus.kt:
--------------------------------------------------------------------------------
1 | package com.example.permission_manager
2 |
3 | enum class TaskyPermissionStatus {
4 | Granted,
5 | DeclinedOnce,
6 | DeclinedPermanently
7 | }
8 |
--------------------------------------------------------------------------------
/core/preferences-manager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/preferences-manager/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.AndroidLibrary)
3 | id(Plugins.Core)
4 | }
5 |
6 | android {
7 | namespace = "pseudoankit.droid.appprefrences"
8 | }
9 |
10 | dependencies {
11 | implementation(libs.datastore)
12 | }
--------------------------------------------------------------------------------
/core/preferences-manager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/preferences-manager/src/main/java/pseudoankit/droid/di/PreferencesModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.di
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.preferences.core.Preferences
6 | import androidx.datastore.preferences.preferencesDataStore
7 | import org.koin.android.ext.koin.androidContext
8 | import org.koin.dsl.module
9 | import pseudoankit.droid.preferencesmanager.PreferenceRepository
10 |
11 | object PreferencesModule {
12 |
13 | private val Context.dataStore: DataStore by preferencesDataStore("tasky-prefs")
14 |
15 | operator fun invoke() = module {
16 | single { PreferenceRepository(datStore = androidContext().dataStore) }
17 | }
18 | }
--------------------------------------------------------------------------------
/core/preferences-manager/src/main/java/pseudoankit/droid/preferencesmanager/BasePreference.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.preferencesmanager
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import androidx.datastore.preferences.core.edit
6 | import androidx.datastore.preferences.core.emptyPreferences
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.catch
9 | import kotlinx.coroutines.flow.distinctUntilChanged
10 | import kotlinx.coroutines.flow.firstOrNull
11 | import kotlinx.coroutines.flow.map
12 |
13 | abstract class BasePreference(
14 | val prefDataStore: DataStore
15 | ) {
16 |
17 | suspend inline fun setValue(key: Preferences.Key, value: T) {
18 | prefDataStore.edit {
19 | it[key] = value
20 | }
21 | }
22 |
23 | suspend inline fun getValue(key: Preferences.Key): T? {
24 | return getValueAsFlow(key).firstOrNull()
25 | }
26 |
27 | inline fun getValueAsFlow(key: Preferences.Key): Flow {
28 | return prefDataStore.data
29 | .catch {
30 | emptyPreferences()
31 | }
32 | .map { prefs ->
33 | prefs[key]
34 | }
35 | .distinctUntilChanged()
36 | }
37 | }
--------------------------------------------------------------------------------
/core/preferences-manager/src/main/java/pseudoankit/droid/preferencesmanager/Keys.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.preferencesmanager
2 |
3 | import androidx.datastore.preferences.core.booleanPreferencesKey
4 | import androidx.datastore.preferences.core.stringPreferencesKey
5 |
6 | internal object Keys {
7 | val isLoggedIn = booleanPreferencesKey("is_logged_in")
8 | }
--------------------------------------------------------------------------------
/core/preferences-manager/src/main/java/pseudoankit/droid/preferencesmanager/PreferenceRepository.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.preferencesmanager
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import kotlinx.coroutines.flow.map
6 |
7 | class PreferenceRepository(
8 | datStore: DataStore
9 | ) : BasePreference(datStore) {
10 |
11 | fun isLoggedInFlow() = getValueAsFlow(Keys.isLoggedIn).map { it == true }
12 | suspend fun isLoggedIn() = getValue(Keys.isLoggedIn)
13 | suspend fun setIsLoggedIn(value: Boolean) = setValue(Keys.isLoggedIn, value)
14 | }
--------------------------------------------------------------------------------
/core/test-helper/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/test-helper/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | }
4 |
5 | android {
6 | namespace = "com.pseudoankit.test_helper"
7 | }
8 |
9 | dependencies {
10 | implementation(libs.bundles.test)
11 | }
--------------------------------------------------------------------------------
/core/test-helper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/test-helper/src/main/java/com/pseudoankit/test_helper/CoroutineDispatcherRule.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.test_helper
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.StandardTestDispatcher
6 | import kotlinx.coroutines.test.TestDispatcher
7 | import kotlinx.coroutines.test.resetMain
8 | import kotlinx.coroutines.test.setMain
9 | import org.junit.rules.TestWatcher
10 | import org.junit.runner.Description
11 | import pseudoankit.droid.core.dispatcher.DispatcherProvider
12 |
13 | @ExperimentalCoroutinesApi
14 | class CoroutineDispatcherRule(
15 | val dispatcher: TestDispatcher = StandardTestDispatcher()
16 | ) : TestWatcher() {
17 |
18 | val dispatcherProvider: DispatcherProvider =
19 | TestCoroutineDispatcherProvider(dispatcher)
20 |
21 | override fun starting(description: Description) {
22 | super.starting(description)
23 | Dispatchers.setMain(dispatcher)
24 | }
25 |
26 | override fun finished(description: Description) {
27 | super.finished(description)
28 | Dispatchers.resetMain()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/test-helper/src/main/java/com/pseudoankit/test_helper/TestCoroutineDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.test_helper
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.withContext
6 | import pseudoankit.droid.core.dispatcher.DispatcherProvider
7 |
8 | class TestCoroutineDispatcherProvider(
9 | private val dispatcher: CoroutineDispatcher
10 | ) : DispatcherProvider {
11 |
12 | override val io: CoroutineDispatcher
13 | get() = dispatcher
14 | override val default: CoroutineDispatcher
15 | get() = dispatcher
16 |
17 | override suspend fun switchToIo(block: suspend CoroutineScope.() -> T): T {
18 | return withContext(dispatcher) {
19 | block()
20 | }
21 | }
22 |
23 | override suspend fun switchToDefault(block: suspend CoroutineScope.() -> T): T {
24 | return withContext(dispatcher) {
25 | block()
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/test-helper/src/main/java/com/pseudoankit/test_helper/TestUtils.kt:
--------------------------------------------------------------------------------
1 | package com.pseudoankit.test_helper
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Job
5 | import kotlinx.coroutines.launch
6 | import kotlinx.coroutines.test.TestScope
7 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
8 |
9 | fun TestScope.launchInTestScope(block: suspend CoroutineScope.() -> Unit): Job {
10 | return launch(UnconfinedTestDispatcher(testScheduler)) {
11 | block()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/feature/agenda/event/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/agenda/event/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | ksp {
7 | arg("compose-destinations.mode", "destinations")
8 | arg("compose-destinations.moduleName", "agenda-task")
9 | }
10 |
11 | android {
12 | namespace = "pseudoankit.droid.tasky.event"
13 | }
--------------------------------------------------------------------------------
/feature/agenda/event/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/agenda/reminder/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/agenda/reminder/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | ksp {
7 | arg("compose-destinations.mode", "destinations")
8 | arg("compose-destinations.moduleName", "agenda-reminder")
9 | }
10 |
11 | android {
12 | namespace = "pseudoankit.droid.tasky.reminder"
13 | }
14 |
15 | dependencies {
16 | implementation(projects.core.agendaManger)
17 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/di/ReminderModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.di
2 |
3 | import org.koin.androidx.viewmodel.dsl.viewModel
4 | import org.koin.core.module.Module
5 | import org.koin.dsl.module
6 | import pseudoankit.droid.core.koin.BaseKoinModule
7 | import pseudoankit.droid.tasky.reminder.presentation.ReminderViewModel
8 |
9 | internal object ReminderModule : BaseKoinModule() {
10 |
11 | override val modules: Module
12 | get() = module {
13 | viewModel { ReminderViewModel(get(), get(), get()) }
14 | }
15 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/navigator/ActionNavTypeSerializer.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.navigator
2 |
3 | import com.ramcosta.composedestinations.navargs.DestinationsNavTypeSerializer
4 | import com.ramcosta.composedestinations.navargs.NavTypeSerializer
5 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
6 |
7 | @NavTypeSerializer
8 | object ActionNavTypeSerializer : DestinationsNavTypeSerializer {
9 | private const val CREATE = 0L
10 |
11 | override fun toRouteString(value: AgendaTypes.Action): String {
12 | return when (value) {
13 | AgendaTypes.Action.Create -> "$CREATE"
14 | is AgendaTypes.Action.Edit -> "${value.id}"
15 | }
16 | }
17 |
18 | override fun fromRouteString(routeStr: String): AgendaTypes.Action {
19 | return when (val id = routeStr.toLongOrNull()) {
20 | null, CREATE -> AgendaTypes.Action.Create
21 | else -> AgendaTypes.Action.Edit(id)
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/navigator/ReminderDeepLinkProvider.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.navigator
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
4 |
5 | interface ReminderDeepLinkProvider {
6 | fun reminderScreenRoute(action: AgendaTypes.Action): String
7 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/navigator/ReminderNavigator.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.navigator
2 |
3 | interface ReminderNavigator {
4 | fun navigateToHomeScreen()
5 | fun navigateUp()
6 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/presentation/ReminderUiState.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.presentation
2 |
3 | import androidx.compose.runtime.Stable
4 | import kotlinx.collections.immutable.toImmutableList
5 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
6 | import pseudoankit.droid.core.model.TaskyDate
7 | import pseudoankit.droid.core.model.TaskyTime
8 | import pseudoankit.droid.core.util.TextResource
9 | import pseudoankit.droid.core.util.extension.parseToString
10 |
11 | internal interface ReminderUiState {
12 |
13 | @Stable
14 | data class State(
15 | val reminderText: String = "",
16 | val remindAllDay: Boolean = false,
17 | val selectedDate: TaskyDate = TaskyDate.Today,
18 | val selectedRepeatInterval: AgendaItem.Reminder.RepeatInterval = AgendaItem.Reminder.RepeatInterval.DoNotRepeat,
19 | private val _selectedTime: TaskyTime = TaskyTime.Now,
20 | val editId: Long? = null
21 | ) {
22 | val selectedTime get() = if (remindAllDay) null else _selectedTime
23 | val displayTime get() = selectedTime.parseToString()
24 | val displayDate get() = selectedDate.parseToString("eee, dd MMM yyyy").orEmpty()
25 | val repeatIntervalItems =
26 | AgendaItem.Reminder.RepeatInterval.values().toList().toImmutableList()
27 | }
28 |
29 | sealed interface SideEffect {
30 | object NavigateUp : SideEffect
31 | object ShowDatePicker : SideEffect
32 | object ShowTimePicker : SideEffect
33 | object ToggleRepeatIntervalSelectionView : SideEffect
34 | object ShowCustomRepeatIntervalSelector : SideEffect
35 | object NavigateToHomeScreen : SideEffect
36 | data class ShowError(val message: TextResource) : SideEffect
37 | }
38 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/presentation/mapper/ReminderMapper.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.presentation.mapper
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
4 | import pseudoankit.droid.core.model.TaskyTime
5 | import pseudoankit.droid.core.util.datetime.TimeUtils.orNow
6 | import pseudoankit.droid.tasky.reminder.presentation.ReminderUiState
7 |
8 | internal object ReminderMapper {
9 |
10 | val ReminderUiState.State.mapToReminderObj
11 | get() = kotlin.run {
12 | AgendaItem.Reminder(
13 | title = reminderText,
14 | date = selectedDate,
15 | time = if (remindAllDay) {
16 | AgendaItem.Reminder.Time.AllDay
17 | } else {
18 | AgendaItem.Reminder.Time.Time(selectedTime.orNow)
19 | },
20 | repeatInterval = selectedRepeatInterval,
21 | id = editId ?: System.currentTimeMillis()
22 | )
23 | }
24 |
25 | val AgendaItem.Reminder.mapToUiState
26 | get() = kotlin.run {
27 | ReminderUiState.State(
28 | reminderText = title,
29 | selectedDate = date,
30 | selectedRepeatInterval = repeatInterval,
31 | remindAllDay = time == AgendaItem.Reminder.Time.AllDay,
32 | _selectedTime = when (time) {
33 | AgendaItem.Reminder.Time.AllDay -> TaskyTime.Now
34 | is AgendaItem.Reminder.Time.Time -> (time as? AgendaItem.Reminder.Time.Time)?.value.orNow
35 | },
36 | editId = id
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/feature/agenda/reminder/src/main/java/pseudoankit/droid/tasky/reminder/presentation/mapper/RepeatIntervalUiMapper.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.reminder.presentation.mapper
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
4 | import pseudoankit.droid.core.util.TextResource
5 |
6 | internal object RepeatIntervalUiMapper {
7 |
8 | val AgendaItem.Reminder.RepeatInterval.label
9 | get() = when (this) {
10 | AgendaItem.Reminder.RepeatInterval.DoNotRepeat -> TextResource.NormalString("Do not repeat")
11 | AgendaItem.Reminder.RepeatInterval.Daily -> TextResource.NormalString("Repeats daily")
12 | AgendaItem.Reminder.RepeatInterval.Weekly -> TextResource.NormalString("Repeats weekly")
13 | AgendaItem.Reminder.RepeatInterval.Monthly -> TextResource.NormalString("Repeats monthly")
14 | AgendaItem.Reminder.RepeatInterval.Yearly -> TextResource.NormalString("Repeats yearly")
15 | AgendaItem.Reminder.RepeatInterval.Custom -> TextResource.NormalString("Custom")
16 | }
17 | }
--------------------------------------------------------------------------------
/feature/agenda/task/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/agenda/task/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | ksp {
7 | arg("compose-destinations.mode", "destinations")
8 | arg("compose-destinations.moduleName", "agenda-task")
9 | }
10 |
11 | android {
12 | namespace = "pseudoankit.droid.tasky.task"
13 | }
--------------------------------------------------------------------------------
/feature/agenda/task/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/authentication/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/authentication/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | ksp {
7 | arg("compose-destinations.mode", "destinations")
8 | arg("compose-destinations.moduleName", "Authentication")
9 | }
10 |
11 | android {
12 | namespace = "pseudoankit.droid.authentication"
13 | }
14 |
15 | dependencies {
16 | implementation(projects.core.preferencesManager)
17 | }
18 |
--------------------------------------------------------------------------------
/feature/authentication/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/di/LoginModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.di
2 |
3 | import org.koin.androidx.viewmodel.dsl.viewModel
4 | import org.koin.core.module.Module
5 | import org.koin.dsl.module
6 | import pseudoankit.droid.authentication.domain.LoginUserUseCase
7 | import pseudoankit.droid.authentication.presentation.login.LoginViewModel
8 | import pseudoankit.droid.core.koin.BaseKoinModule
9 |
10 | internal object LoginModule : BaseKoinModule() {
11 |
12 | override val modules: Module
13 | get() = module {
14 | factory { LoginUserUseCase(get()) }
15 |
16 | viewModel { LoginViewModel(inject(), get()) }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/di/RegistrationModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.di
2 |
3 | import org.koin.androidx.viewmodel.dsl.viewModel
4 | import org.koin.core.module.Module
5 | import org.koin.dsl.module
6 | import pseudoankit.droid.authentication.domain.RegisterUserUseCase
7 | import pseudoankit.droid.authentication.presentation.registration.RegistrationViewModel
8 | import pseudoankit.droid.core.koin.BaseKoinModule
9 |
10 | internal object RegistrationModule : BaseKoinModule() {
11 |
12 | override val modules: Module
13 | get() = module {
14 | factory { RegisterUserUseCase() }
15 | viewModel { RegistrationViewModel(get(), get()) }
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/domain/LoginUserUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.domain
2 |
3 | import kotlinx.coroutines.delay
4 | import pseudoankit.droid.authentication.presentation.login.LoginUiState
5 | import pseudoankit.droid.core.util.TextResource
6 | import pseudoankit.droid.core.util.extension.safeCall
7 | import pseudoankit.droid.core.util.validator.Validator
8 | import pseudoankit.droid.preferencesmanager.PreferenceRepository
9 |
10 | internal class LoginUserUseCase(
11 | private val preferenceRepository: PreferenceRepository
12 | ) {
13 |
14 | sealed interface Result {
15 | data class EmailError(val message: TextResource?) : Result
16 | data class PasswordError(val message: TextResource?) : Result
17 | data class Error(val message: TextResource) : Result
18 | object Success : Result
19 | }
20 |
21 | suspend operator fun invoke(state: LoginUiState.State): Result {
22 | return safeCall(
23 | block = {
24 | val emailError = Validator.validateEmail(email = state.emailConfig.value)
25 | if (emailError != null) {
26 | return Result.EmailError(emailError)
27 | }
28 |
29 | val passwordError =
30 | Validator.validatePassword(password = state.passwordConfig.value)
31 | if (passwordError != null) {
32 | return Result.PasswordError(passwordError)
33 | }
34 |
35 | delay(500)
36 | preferenceRepository.setIsLoggedIn(value = true)
37 | Result.Success
38 | },
39 | onError = {
40 | Result.Error(it)
41 | }
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/domain/RegisterUserUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.domain
2 |
3 | import kotlinx.coroutines.delay
4 | import pseudoankit.droid.authentication.presentation.registration.RegistrationUiState
5 | import pseudoankit.droid.core.util.TextResource
6 | import pseudoankit.droid.core.util.extension.safeCall
7 | import pseudoankit.droid.core.util.validator.Validator
8 |
9 | internal class RegisterUserUseCase {
10 |
11 | sealed interface Result {
12 | data class NameError(val message: TextResource?) : Result
13 | data class EmailError(val message: TextResource?) : Result
14 | data class PasswordError(val message: TextResource?) : Result
15 | data class Error(val message: TextResource) : Result
16 | object Success : Result
17 | }
18 |
19 | suspend operator fun invoke(state: RegistrationUiState.State): Result {
20 | return safeCall(
21 | block = {
22 | val nameError = Validator.validateName(name = state.nameConfig.value)
23 | if (nameError != null) {
24 | return Result.NameError(nameError)
25 | }
26 |
27 | val emailError = Validator.validateEmail(email = state.emailConfig.value)
28 | if (emailError != null) {
29 | return Result.EmailError(emailError)
30 | }
31 |
32 | val passwordError =
33 | Validator.validatePassword(password = state.passwordConfig.value)
34 | if (passwordError != null) {
35 | return Result.PasswordError(passwordError)
36 | }
37 |
38 | delay(500)
39 |
40 | return Result.Success
41 | },
42 | onError = {
43 | Result.Error(it)
44 | }
45 | )
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/navigator/AuthNavigator.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.navigator
2 |
3 | interface AuthNavigator {
4 | fun navigateUp()
5 | fun navigateToRegistrationScreen()
6 | fun navigateToHomeScreen()
7 | }
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/presentation/login/LoginUiState.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.presentation.login
2 |
3 | import pseudoankit.droid.coreui.model.TextFieldUiConfig
4 | import pseudoankit.droid.unify.component.button.UnifyButtonConfig
5 |
6 | object LoginUiState {
7 |
8 | // TEST remove def values later on
9 | internal data class State(
10 | val emailConfig: TextFieldUiConfig = TextFieldUiConfig(
11 | value = "lostankit7@gmail.com",
12 | errorMessage = null
13 | ),
14 | val passwordConfig: TextFieldUiConfig = TextFieldUiConfig(
15 | value = "qwerty",
16 | errorMessage = null
17 | ),
18 | val isLoginInProgress: Boolean = false
19 | ) {
20 | val buttonState
21 | get() = UnifyButtonConfig.State.fromBoolean(
22 | isValid = emailConfig.errorMessage == null && passwordConfig.errorMessage == null,
23 | isLoading = isLoginInProgress
24 | )
25 | }
26 |
27 | internal sealed interface SideEffect {
28 | object NavigateToRegistrationScreen : SideEffect
29 | object NavigateToHomeScreen : SideEffect
30 | }
31 | }
--------------------------------------------------------------------------------
/feature/authentication/src/main/java/pseudoankit/droid/authentication/presentation/registration/RegistrationUiState.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.authentication.presentation.registration
2 |
3 | import pseudoankit.droid.core.util.TextResource
4 | import pseudoankit.droid.coreui.model.TextFieldUiConfig
5 | import pseudoankit.droid.unify.component.button.UnifyButtonConfig
6 |
7 | interface RegistrationUiState {
8 | data class State(
9 | val emailConfig: TextFieldUiConfig = TextFieldUiConfig(
10 | value = "",
11 | errorMessage = null
12 | ),
13 | val passwordConfig: TextFieldUiConfig = TextFieldUiConfig(
14 | value = "",
15 | errorMessage = null
16 | ),
17 | val nameConfig: TextFieldUiConfig = TextFieldUiConfig(
18 | value = "",
19 | errorMessage = null
20 | ),
21 | val isRegistrationInProgress: Boolean = false
22 | ) {
23 | val doneBtnState = UnifyButtonConfig.State.fromBoolean(
24 | isValid = emailConfig.errorMessage == null && passwordConfig.errorMessage == null && nameConfig.errorMessage == null,
25 | isLoading = isRegistrationInProgress
26 | )
27 | }
28 |
29 | sealed interface SideEffect {
30 | object NavigateBack : SideEffect
31 | data class ShowErrorMessage(val message: TextResource) : SideEffect
32 | }
33 |
34 | sealed interface Event {
35 | data class OnNameChanged(val value: String) : Event
36 | data class OnEmailChanged(val value: String) : Event
37 | data class OnPasswordChanged(val value: String) : Event
38 | object OnNavigateBack : Event
39 | object OnRegisterUser : Event
40 | }
41 | }
--------------------------------------------------------------------------------
/feature/authentication/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Welcome Back
4 |
--------------------------------------------------------------------------------
/feature/developer-tools/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/developer-tools/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | }
4 |
5 | android {
6 | namespace = "pseudoankit.tasky.developer_tools"
7 | }
8 |
9 | dependencies {
10 | debugImplementation(libs.leakCanary)
11 | }
--------------------------------------------------------------------------------
/feature/developer-tools/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/home/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/home/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | // todo create a utility
7 | ksp {
8 | arg("compose-destinations.mode", "destinations")
9 | arg("compose-destinations.moduleName", "Home")
10 | }
11 |
12 | android {
13 | namespace = "pseudoankit.droid.tasky.home"
14 | }
15 |
16 | dependencies {
17 | implementation(projects.core.agendaManger)
18 | implementation(projects.core.permissionManager)
19 | }
--------------------------------------------------------------------------------
/feature/home/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/di/AgendaItemScreenModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.di
2 |
3 | import org.koin.androidx.viewmodel.dsl.viewModel
4 | import org.koin.core.module.Module
5 | import org.koin.dsl.module
6 | import pseudoankit.droid.core.koin.BaseKoinModule
7 | import pseudoankit.droid.tasky.home.presentation.taskyitems.AgendaItemsViewModel
8 |
9 | object AgendaItemScreenModule : BaseKoinModule() {
10 | override val modules: Module
11 | get() = module {
12 | viewModel { AgendaItemsViewModel() }
13 | }
14 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/di/HomeScreenModule.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.di
2 |
3 | import org.koin.androidx.viewmodel.dsl.viewModel
4 | import org.koin.core.module.Module
5 | import org.koin.dsl.module
6 | import pseudoankit.droid.agendamanger.domain.usecase.reminder.GetSavedAgendaItemsUseCase
7 | import pseudoankit.droid.core.koin.BaseKoinModule
8 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlowInstance
9 | import pseudoankit.droid.tasky.home.domain.usecase.DeleteAgendaUseCase
10 | import pseudoankit.droid.tasky.home.domain.usecase.ToggleAgendaItemCompletionUseCase
11 | import pseudoankit.droid.tasky.home.presentation.home.HomeViewModel
12 |
13 | internal object HomeScreenModule : BaseKoinModule() {
14 |
15 | override val modules: Module
16 | get() = module {
17 | factory { GetSavedAgendaItemsUseCase(get()) }
18 | factory { ToggleAgendaItemCompletionUseCase(get(), get(), get()) }
19 | factory { DeleteAgendaUseCase(get(), UpdateAppWidgetFlowInstance) }
20 |
21 | viewModel { HomeViewModel(get(), get(), get()) }
22 | }
23 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/domain/usecase/DeleteAgendaUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.domain.usecase
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
4 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepository
5 | import pseudoankit.droid.core.util.TaskyResult
6 | import pseudoankit.droid.core.util.extension.safeCall
7 | import pseudoankit.droid.core.widget.UpdateAppWidgetFlow
8 |
9 | class DeleteAgendaUseCase(
10 | private val reminderRepository: ReminderRepository,
11 | private val updateAppWidgetFlow: UpdateAppWidgetFlow
12 | ) {
13 |
14 | suspend operator fun invoke(agendaItem: AgendaItem) = safeCall {
15 | when (agendaItem) {
16 | is AgendaItem.Event -> TODO()
17 | is AgendaItem.Reminder -> reminderRepository.delete(agendaItem)
18 | is AgendaItem.Task -> TODO()
19 | }
20 |
21 | updateAppWidgetFlow.emit(Unit)
22 | TaskyResult.Success(Unit)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/domain/usecase/ToggleAgendaItemCompletionUseCase.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.domain.usecase
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
4 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
5 | import pseudoankit.droid.agendamanger.domain.repository.ReminderRepository
6 | import pseudoankit.droid.agendamanger.domain.usecase.reminder.SaveReminderUseCase
7 | import pseudoankit.droid.tasky.home.navigator.HomeDeepLinkProvider
8 |
9 | internal class ToggleAgendaItemCompletionUseCase(
10 | private val reminderRepository: ReminderRepository,
11 | private val saveReminderUseCase: SaveReminderUseCase,
12 | private val deepLinkProvider: HomeDeepLinkProvider
13 | ) {
14 |
15 | suspend operator fun invoke(agenda: AgendaItem) {
16 | when (agenda) {
17 | is AgendaItem.Event -> TODO()
18 | is AgendaItem.Reminder -> saveReminderUseCase(
19 | payload = agenda.copy(completed = agenda.completed.not()),
20 | alarmDeepLink = deepLinkProvider.reminderScreenRoute(AgendaTypes.Action.Edit(agenda.id))
21 | )
22 | is AgendaItem.Task -> TODO()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/navigator/HomeDeepLinkProvider.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.navigator
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
4 |
5 | interface HomeDeepLinkProvider {
6 | fun reminderScreenRoute(action: AgendaTypes.Action): String
7 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/navigator/HomeScreenNavigator.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.navigator
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
4 |
5 | interface HomeScreenNavigator {
6 | fun navigateUp()
7 | fun navigateToAgendaItemsSelectorScreen()
8 | fun navigateToAgendaScreen(agendaTypes: AgendaTypes)
9 |
10 | fun navigateToProfileScreen()
11 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/presentation/home/HomeUiState.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.presentation.home
2 |
3 | import kotlinx.collections.immutable.ImmutableList
4 | import kotlinx.collections.immutable.persistentListOf
5 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
6 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
7 | import pseudoankit.droid.core.model.TaskyDate
8 | import pseudoankit.droid.core.util.datetime.DateUtils
9 | import pseudoankit.droid.core.util.extension.parseToString
10 |
11 | internal interface HomeUiState {
12 |
13 | sealed interface SideEffect {
14 | object ShowDatePicker : SideEffect
15 | data class HighlightCurrentSelectedDate(val position: Int) : SideEffect
16 | object ShowAgendaItems : SideEffect
17 | data class NavigateToAgendaScreen(val agendaTypes: AgendaTypes) : SideEffect
18 | object ShowProfileIcon: SideEffect
19 | }
20 |
21 | data class State(
22 | val selectedDate: TaskyDate = TaskyDate.Today,
23 | val savedAgendaItems: ImmutableList = persistentListOf()
24 | ) {
25 | val selectedMonthDateRange: ImmutableList =
26 | DateUtils.getDateRangeForMonth(selectedDate)
27 |
28 | val displayHeaderDate get() = selectedDate.parseToString("MMM yyyy").orEmpty()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/presentation/mapper/AgendaItemsUiMapper.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.presentation.mapper
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaItem
4 | import pseudoankit.droid.core.util.extension.parseToString
5 | import pseudoankit.droid.unify.token.UnifyColors
6 |
7 | internal object AgendaItemsUiMapper {
8 | val AgendaItem.Reminder.displayDateTime: String?
9 | get() = when (time) {
10 | AgendaItem.Reminder.Time.AllDay -> null
11 | is AgendaItem.Reminder.Time.Time -> {
12 | (time as? AgendaItem.Reminder.Time.Time)?.value.parseToString()
13 | }
14 | }
15 |
16 | val AgendaItem.backgroundColor
17 | get() = when (this) {
18 | is AgendaItem.Reminder -> UnifyColors.Green400
19 | is AgendaItem.Event -> UnifyColors.Blue200
20 | is AgendaItem.Task -> UnifyColors.Yellow300
21 | }
22 |
23 | val AgendaItem.tint get() = UnifyColors.White
24 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/presentation/mapper/AgendaTypesUiMapper.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.presentation.mapper
2 |
3 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
4 | import pseudoankit.droid.core.util.TextResource
5 | import pseudoankit.droid.unify.component.icon.UnifyIcons
6 |
7 | internal object AgendaTypesUiMapper {
8 |
9 | val AgendaTypes.label
10 | get() = when (this) {
11 | is AgendaTypes.Reminder -> TextResource.NormalString("Reminder")
12 | is AgendaTypes.Event -> TextResource.NormalString("Event")
13 | is AgendaTypes.Task -> TextResource.NormalString("Task")
14 | }
15 |
16 | val AgendaTypes.icon
17 | get() = when (this) {
18 | is AgendaTypes.Reminder -> UnifyIcons.Bell
19 | is AgendaTypes.Event -> UnifyIcons.Calendar
20 | is AgendaTypes.Task -> UnifyIcons.Task
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/presentation/taskyitems/AgendaItemsUiState.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.presentation.taskyitems
2 |
3 | import kotlinx.collections.immutable.ImmutableList
4 | import kotlinx.collections.immutable.persistentListOf
5 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
6 |
7 | internal interface AgendaItemsUiState {
8 |
9 | data class State(
10 | val items: ImmutableList = persistentListOf(
11 | AgendaTypes.Reminder(AgendaTypes.Action.Create),
12 | AgendaTypes.Task(AgendaTypes.Action.Create),
13 | AgendaTypes.Event(AgendaTypes.Action.Create),
14 | )
15 | )
16 |
17 | sealed interface SideEffect {
18 | object NavigateUp : SideEffect
19 | data class NavigateToAgenda(val type: AgendaTypes) : SideEffect
20 | }
21 | }
--------------------------------------------------------------------------------
/feature/home/src/main/java/pseudoankit/droid/tasky/home/presentation/taskyitems/AgendaItemsViewModel.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.tasky.home.presentation.taskyitems
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import org.orbitmvi.orbit.Container
6 | import org.orbitmvi.orbit.ContainerHost
7 | import org.orbitmvi.orbit.container
8 | import pseudoankit.droid.agendamanger.domain.model.AgendaTypes
9 | import pseudoankit.droid.coreui.util.extension.postSideEffect
10 |
11 | internal class AgendaItemsViewModel : ViewModel(),
12 | ContainerHost {
13 |
14 | override val container: Container =
15 | viewModelScope.container(AgendaItemsUiState.State())
16 |
17 | fun onNavigateUp() = postSideEffect {
18 | AgendaItemsUiState.SideEffect.NavigateUp
19 | }
20 |
21 | fun onAgendaSelected(type: AgendaTypes) = postSideEffect {
22 | AgendaItemsUiState.SideEffect.NavigateToAgenda(type)
23 | }
24 | }
--------------------------------------------------------------------------------
/feature/profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature/profile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.CoreFeatureLib)
3 | id(Plugins.ComposeFeatureLib)
4 | }
5 |
6 | ksp {
7 | arg("compose-destinations.mode", "destinations")
8 | arg("compose-destinations.moduleName", "Profile")
9 | }
10 |
11 | android {
12 | namespace = "pseudoankit.droid.profile"
13 | }
14 |
--------------------------------------------------------------------------------
/feature/profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/feature/profile/src/main/java/pseudoankit/droid/profile/presentation/ui/ProfileScreen.kt:
--------------------------------------------------------------------------------
1 | package pseudoankit.droid.profile.presentation.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.ramcosta.composedestinations.annotation.DeepLink
5 | import com.ramcosta.composedestinations.annotation.Destination
6 | import pseudoankit.droid.core.deeplink.TaskyDeeplink
7 |
8 | @Destination(
9 | deepLinks = [
10 | DeepLink(uriPattern = TaskyDeeplink.profile)
11 | ]
12 | )
13 | @Composable
14 | internal fun ProfileScreen() {
15 |
16 | }
--------------------------------------------------------------------------------
/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
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 | systemProp.sonar.sources=./src/main
27 | systemProp.sonar.host.url=https://sonarcloud.io/
28 | systemProp.sonar.organization=pseudoankit
29 | systemProp.sonar.projectKey=pseudoankit_Tasky
30 | systemProp.sonar.projectName=Tasky
31 | android.defaults.buildfeatures.buildconfig=true
32 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/preview/add-reminder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/preview/add-reminder.png
--------------------------------------------------------------------------------
/preview/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/preview/home.png
--------------------------------------------------------------------------------
/preview/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/preview/login.png
--------------------------------------------------------------------------------
/preview/shortcut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/preview/shortcut.png
--------------------------------------------------------------------------------
/preview/widget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ankitk77/tasky/5cbe268aa0bc0ccd121eeccc42c60aea6815078f/preview/widget.png
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base",
5 | "group:all",
6 | ":dependencyDashboard",
7 | "schedule:daily"
8 | ],
9 | "baseBranches": [
10 | "master"
11 | ],
12 | "commitMessageExtra": "{{{currentValue}}} to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}",
13 | "packageRules": [
14 | {
15 | "matchPackagePatterns": [
16 | "androidx.compose.compiler:compiler"
17 | ],
18 | "groupName": "kotlin"
19 | },
20 | {
21 | "matchPackagePatterns": [
22 | "org.jetbrains.kotlin.*"
23 | ],
24 | "groupName": "kotlin"
25 | },
26 | {
27 | "matchPackagePatterns": [
28 | "com.google.devtools.ksp"
29 | ],
30 | "groupName": "kotlin"
31 | }
32 | ]
33 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
2 |
3 | rootProject.name = "Tasky"
4 | include(":app")
5 |
6 | include(":core:core")
7 | include(":core:core-ui")
8 | include(":core:design-system")
9 | include(":core:agenda-manger")
10 | include(":core:database-manager")
11 | include(":core:preferences-manager")
12 | include(":core:alarm-manager")
13 | include(":core:notification-manager")
14 | include(":core:permission-manager")
15 | include(":core:app-shortcuts-n-widgets")
16 | include(":core:test-helper")
17 |
18 | include(":feature:authentication")
19 | include(":feature:home")
20 | include(":feature:agenda:event")
21 | include(":feature:agenda:reminder")
22 | include(":feature:agenda:task")
23 | include(":feature:profile")
24 | include(":feature:developer-tools")
25 |
26 | include(":benchmark")
27 |
--------------------------------------------------------------------------------