├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
├── labeler.yml
└── workflows
│ ├── android.yml
│ └── labeler.yml
├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── google-services.json
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── drunkenboys
│ │ └── calendarun
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── default.db
│ │ └── switching_calendar.json
│ ├── java
│ │ └── com
│ │ │ └── drunkenboys
│ │ │ └── calendarun
│ │ │ ├── App.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── SavedStateKeys.kt
│ │ │ ├── data
│ │ │ ├── calendar
│ │ │ │ ├── entity
│ │ │ │ │ └── Calendar.kt
│ │ │ │ └── local
│ │ │ │ │ ├── CalendarDao.kt
│ │ │ │ │ ├── CalendarLocalDataSource.kt
│ │ │ │ │ └── CalendarLocalDataSourceImpl.kt
│ │ │ ├── calendartheme
│ │ │ │ ├── entity
│ │ │ │ │ └── CalendarTheme.kt
│ │ │ │ └── local
│ │ │ │ │ ├── CalendarThemeDao.kt
│ │ │ │ │ ├── CalendarThemeLocalDataSource.kt
│ │ │ │ │ └── CalendarThemeLocalDataSourceImpl.kt
│ │ │ ├── holiday
│ │ │ │ ├── ResponseHolidayInfo.kt
│ │ │ │ ├── entity
│ │ │ │ │ └── Holiday.kt
│ │ │ │ ├── local
│ │ │ │ │ ├── HolidayDao.kt
│ │ │ │ │ ├── HolidayLocalDataSource.kt
│ │ │ │ │ └── HolidayLocalDataSourceImpl.kt
│ │ │ │ ├── remote
│ │ │ │ │ ├── HolidayRemoteDataSource.kt
│ │ │ │ │ ├── HolidayRemoteDataSourceImpl.kt
│ │ │ │ │ └── HolidayRemoteService.kt
│ │ │ │ └── repository
│ │ │ │ │ ├── HolidayRepository.kt
│ │ │ │ │ └── HolidayRepositoryImpl.kt
│ │ │ ├── room
│ │ │ │ ├── Converters.kt
│ │ │ │ └── Database.kt
│ │ │ ├── schedule
│ │ │ │ ├── entity
│ │ │ │ │ └── Schedule.kt
│ │ │ │ └── local
│ │ │ │ │ ├── ScheduleDao.kt
│ │ │ │ │ ├── ScheduleLocalDataSource.kt
│ │ │ │ │ └── ScheduleLocalDataSourceImpl.kt
│ │ │ └── slice
│ │ │ │ ├── entity
│ │ │ │ └── Slice.kt
│ │ │ │ └── local
│ │ │ │ ├── SliceDao.kt
│ │ │ │ ├── SliceLocalDataSource.kt
│ │ │ │ └── SliceLocalDataSourceImpl.kt
│ │ │ ├── di
│ │ │ ├── CoroutineModule.kt
│ │ │ ├── DataSourceModule.kt
│ │ │ ├── HolidayRetrofitServiceModule.kt
│ │ │ ├── LocalDatabaseModule.kt
│ │ │ ├── RepositoryModule.kt
│ │ │ └── RetrofitModule.kt
│ │ │ ├── receiver
│ │ │ ├── BootReceiver.kt
│ │ │ └── ScheduleAlarmReceiver.kt
│ │ │ ├── ui
│ │ │ ├── appinfo
│ │ │ │ └── AppInfoFragment.kt
│ │ │ ├── appwidget
│ │ │ │ ├── CalendaRunAppWidget.kt
│ │ │ │ ├── CalendaRunRemoteViewsFactory.kt
│ │ │ │ └── CalendaRunRemoteViewsService.kt
│ │ │ ├── base
│ │ │ │ ├── BaseActivity.kt
│ │ │ │ ├── BaseFragment.kt
│ │ │ │ └── BaseViewHolder.kt
│ │ │ ├── dayschedule
│ │ │ │ ├── DayScheduleAdapter.kt
│ │ │ │ ├── DayScheduleDialog.kt
│ │ │ │ └── DayScheduleViewModel.kt
│ │ │ ├── maincalendar
│ │ │ │ ├── LoadingDialog.kt
│ │ │ │ ├── MainCalendarFragment.kt
│ │ │ │ └── MainCalendarViewModel.kt
│ │ │ ├── managecalendar
│ │ │ │ ├── DeleteCalendarDialog.kt
│ │ │ │ ├── ManageCalendarAdapter.kt
│ │ │ │ ├── ManageCalendarFragment.kt
│ │ │ │ ├── ManageCalendarViewModel.kt
│ │ │ │ └── model
│ │ │ │ │ └── CalendarItem.kt
│ │ │ ├── savecalendar
│ │ │ │ ├── SaveCalendarAdapter.kt
│ │ │ │ ├── SaveCalendarFragment.kt
│ │ │ │ ├── SaveCalendarViewModel.kt
│ │ │ │ └── model
│ │ │ │ │ └── SliceItem.kt
│ │ │ ├── saveschedule
│ │ │ │ ├── DeleteScheduleDialog.kt
│ │ │ │ ├── SaveScheduleFragment.kt
│ │ │ │ ├── SaveScheduleViewModel.kt
│ │ │ │ └── model
│ │ │ │ │ ├── BehaviorType.kt
│ │ │ │ │ └── DateType.kt
│ │ │ ├── searchschedule
│ │ │ │ ├── SearchScheduleAdapter.kt
│ │ │ │ ├── SearchScheduleDivider.kt
│ │ │ │ ├── SearchScheduleFragment.kt
│ │ │ │ ├── SearchScheduleViewModel.kt
│ │ │ │ └── model
│ │ │ │ │ └── DateScheduleItem.kt
│ │ │ ├── setting
│ │ │ │ ├── SettingFragment.kt
│ │ │ │ └── SettingViewModel.kt
│ │ │ └── theme
│ │ │ │ ├── CalendarThemeMapper.kt
│ │ │ │ ├── ThemeFragment.kt
│ │ │ │ ├── ThemeResetDialog.kt
│ │ │ │ ├── ThemeViewModel.kt
│ │ │ │ └── model
│ │ │ │ └── ThemeColorType.kt
│ │ │ ├── util
│ │ │ ├── DateConverter.kt
│ │ │ ├── HorizontalInsetDividerDecoration.kt
│ │ │ └── extensions
│ │ │ │ ├── AnimationExt.kt
│ │ │ │ ├── ContextExt.kt
│ │ │ │ ├── FlowExt.kt
│ │ │ │ ├── FragmentExt.kt
│ │ │ │ ├── NavigationExt.kt
│ │ │ │ ├── NetworkStateUtil.kt
│ │ │ │ ├── PendingIntentExt.kt
│ │ │ │ ├── ViewExt.kt
│ │ │ │ └── ViewHolderExt.kt
│ │ │ └── view
│ │ │ ├── ColorIndicatorTextView.kt
│ │ │ ├── ErrorGuideEditText.kt
│ │ │ ├── ErrorGuideTextView.kt
│ │ │ └── ExpandableLinearLayout.kt
│ └── res
│ │ ├── anim
│ │ ├── hide_scale_down.xml
│ │ ├── show_scale_up.xml
│ │ ├── slide_in_left.xml
│ │ ├── slide_in_right.xml
│ │ ├── slide_out_left.xml
│ │ └── slide_out_right.xml
│ │ ├── color
│ │ ├── navigation_item_background_color.xml
│ │ └── navigation_item_color.xml
│ │ ├── drawable-v21
│ │ ├── app_widget_background.xml
│ │ └── app_widget_inner_view_background.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xhdpi
│ │ └── ic_check_box_normal_18.xml
│ │ ├── drawable
│ │ ├── bg_on_surface_radius_8dp.xml
│ │ ├── bg_tag_color.xml
│ │ ├── bg_tag_color_black_stroke_grey.xml
│ │ ├── bg_tag_color_white_stroke_grey.xml
│ │ ├── bg_white_radius_24dp.xml
│ │ ├── btn_rect_black_background.xml
│ │ ├── ic_add.xml
│ │ ├── ic_add_circle_24.xml
│ │ ├── ic_assignment.xml
│ │ ├── ic_baseline_check_box_24.xml
│ │ ├── ic_baseline_check_box_outline_blank_24.xml
│ │ ├── ic_calendar_today.xml
│ │ ├── ic_calendar_today_white.xml
│ │ ├── ic_check_box_pressed_18.xml
│ │ ├── ic_delete.xml
│ │ ├── ic_favorite_24.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_license.xml
│ │ ├── ic_list.xml
│ │ ├── ic_main_background.xml
│ │ ├── ic_menu_24.xml
│ │ ├── ic_notifications.xml
│ │ ├── ic_palette.xml
│ │ ├── ic_refresh.xml
│ │ ├── ic_search_white.xml
│ │ ├── ic_settings.xml
│ │ ├── ic_share_white.xml
│ │ ├── ic_text_fields.xml
│ │ └── selector_check_box_18.xml
│ │ ├── font
│ │ └── inter_medium.ttf
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── calendarun_app_widget.xml
│ │ ├── dialog_day_schedule.xml
│ │ ├── dialog_loading.xml
│ │ ├── drawer_header.xml
│ │ ├── fragment_app_info.xml
│ │ ├── fragment_main_calendar.xml
│ │ ├── fragment_manage_calendar.xml
│ │ ├── fragment_save_calendar.xml
│ │ ├── fragment_save_schedule.xml
│ │ ├── fragment_search_schedule.xml
│ │ ├── fragment_setting.xml
│ │ ├── fragment_theme.xml
│ │ ├── item_app_widget_schedule.xml
│ │ ├── item_calendar.xml
│ │ ├── item_date.xml
│ │ ├── item_drop_down_list.xml
│ │ ├── item_schedule.xml
│ │ ├── item_slice.xml
│ │ ├── view_color_set.xml
│ │ └── view_pick_tag_color.xml
│ │ ├── menu
│ │ ├── menu_main_calendar_nav_drawer.xml
│ │ ├── menu_main_calendar_toolbar.xml
│ │ ├── menu_manage_calendar_toolbar.xml
│ │ ├── menu_save_calendar_toolbar.xml
│ │ ├── menu_save_schedule_toolbar.xml
│ │ └── menu_theme_toolbar.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ ├── ic_launcher_round.xml
│ │ ├── ic_main.xml
│ │ └── ic_main_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_round.webp
│ │ ├── ic_main.png
│ │ ├── ic_main_foreground.png
│ │ └── ic_main_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_round.webp
│ │ ├── ic_main.png
│ │ ├── ic_main_foreground.png
│ │ └── ic_main_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_round.webp
│ │ ├── ic_main.png
│ │ ├── ic_main_foreground.png
│ │ └── ic_main_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_round.webp
│ │ ├── ic_main.png
│ │ ├── ic_main_foreground.png
│ │ └── ic_main_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_round.webp
│ │ ├── ic_main.png
│ │ ├── ic_main_foreground.png
│ │ └── ic_main_round.png
│ │ ├── navigation
│ │ └── nav_main.xml
│ │ ├── values-night-v31
│ │ └── themes.xml
│ │ ├── values-night
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-v31
│ │ ├── styles.xml
│ │ └── themes.xml
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ │ ├── xml-v31
│ │ └── calenda_run_app_widget_info.xml
│ │ └── xml
│ │ └── calenda_run_app_widget_info.xml
│ └── test
│ └── java
│ └── com
│ └── drunkenboys
│ └── calendarun
│ ├── ExampleUnitTest.kt
│ ├── data
│ ├── calendar
│ │ └── local
│ │ │ └── FakeCalendarLocalDataSource.kt
│ ├── schedule
│ │ └── local
│ │ │ └── FakeScheduleLocalDataSource.kt
│ └── slice
│ │ └── local
│ │ └── FakeSliceLocalDataSource.kt
│ └── ui
│ ├── savecalendar
│ └── SaveCalendarViewModelTest.kt
│ ├── saveschedule
│ └── SaveScheduleViewModelTest.kt
│ └── searchschedule
│ └── SearchScheduleViewModelTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── library
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── drunkenboys
│ │ └── ckscalendar
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_main-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── drunkenboys
│ │ │ └── ckscalendar
│ │ │ ├── WeekCalendarView.kt
│ │ │ ├── data
│ │ │ ├── CalendarDate.kt
│ │ │ ├── CalendarDesignObject.kt
│ │ │ ├── CalendarScheduleObject.kt
│ │ │ ├── CalendarSet.kt
│ │ │ ├── DayType.kt
│ │ │ └── ScheduleColorType.kt
│ │ │ ├── listener
│ │ │ ├── OnDayClickListener.kt
│ │ │ └── OnDaySecondClickListener.kt
│ │ │ ├── monthcalendar
│ │ │ ├── MonthAdapter.kt
│ │ │ ├── MonthCalendarView.kt
│ │ │ ├── MonthCellFactory.kt
│ │ │ ├── MonthCellPositionStore.kt
│ │ │ ├── MonthHeaderItemDecorator.kt
│ │ │ ├── MonthPageAdapter.kt
│ │ │ ├── MonthState.kt
│ │ │ └── OnDaySelectStateListener.kt
│ │ │ ├── utils
│ │ │ ├── CalendarSetConverter.kt
│ │ │ ├── ComposeStateExtensions.kt
│ │ │ ├── DateConverter.kt
│ │ │ ├── GravityMapper.kt
│ │ │ ├── TimeUtils.kt
│ │ │ └── ViewExtensions.kt
│ │ │ ├── weekcalendar
│ │ │ └── WeekCalendar.kt
│ │ │ └── yearcalendar
│ │ │ ├── CustomTheme.kt
│ │ │ ├── YearCalendarView.kt
│ │ │ ├── YearCalendarViewModel.kt
│ │ │ └── composeView
│ │ │ ├── BaseText.kt
│ │ │ ├── CalendarLazyColumn.kt
│ │ │ ├── DayText.kt
│ │ │ ├── MonthHeader.kt
│ │ │ ├── README.md
│ │ │ ├── ScheduleText.kt
│ │ │ ├── WeekCalendar.kt
│ │ │ ├── WeekHeader.kt
│ │ │ └── YearHeader.kt
│ └── res
│ │ ├── drawable
│ │ ├── bg_calendar_line_05px.xml
│ │ ├── bg_calendar_today.xml
│ │ ├── bg_month_date_selected.xml
│ │ └── ic_main_background.xml
│ │ ├── layout
│ │ ├── item_month_cell.xml
│ │ ├── item_month_page.xml
│ │ ├── layout_month_calendar.xml
│ │ ├── layout_week_calendar.xml
│ │ └── layout_year_calendar.xml
│ │ ├── values-night
│ │ └── colors.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── drunkenboys
│ └── ckscalendar
│ ├── ExampleUnitTest.kt
│ ├── utils
│ ├── CalendarSetConverterTest.kt
│ └── UtilsTest.kt
│ └── yearcalendar
│ └── YearCalendarViewModelTest.kt
└── settings.gradle
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @boostcampwm-2021/andrunken @shshksh @Cha-Ji @PsPLoG @simeunseok
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: feature 이슈 템플릿
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **사전 작업**
11 | - 라벨 달기(app, library, chore, bug, hotfix, feature)
12 | - project달기
13 | - 마일스톤 달기
14 | - asignees 지정
15 |
16 | ## 진행 상황(option)
17 | - [ ]
18 | - [ ]
19 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## what is this pr
2 |
4 |
5 | ## Changes
6 |
7 |
8 | ## screenshot
9 |
10 |
11 |
12 | ## testchecklist
13 |
14 |
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | library:
2 | - library/**/*
3 |
4 | app:
5 | - app/**/*
6 |
7 | test:
8 | - app/src/test/**/*
9 | - app/src/androidTest/**/*
10 | - library/src/test/**/*
11 | - library/src/androidTest/**/*
12 |
13 | chore:
14 | - app/*.gradle
15 | - library/*.gradle
16 | - build.gradle
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | pull_request:
5 | branches: [ develop, master ]
6 |
7 | jobs:
8 | build:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: set up JDK 11
15 | uses: actions/setup-java@v2
16 | with:
17 | java-version: '11'
18 | distribution: 'adopt'
19 | cache: gradle
20 |
21 | - name: Grant execute permission for gradlew
22 | run: chmod +x gradlew
23 |
24 | - name: generate local.properties
25 | run: |
26 | echo ${{ secrets.HOLIDAY_API_KEY }}
27 | echo ${{ secrets.HOLIDAY_API_KEY }} > ./local.properties
28 | cat ./local.properties
29 |
30 | - name: Build with Gradle
31 | run: ./gradlew build
32 |
33 | - name: Unit Test
34 | run: ./gradlew testDebugUnitTest
35 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yml:
--------------------------------------------------------------------------------
1 | name: "Pull Request Labeler"
2 | on:
3 | pull_request_target:
4 | branches:
5 | - develop
6 |
7 | jobs:
8 | label:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/labeler@v2
13 | with:
14 | repo-token: "${{ secrets.GITHUB_TOKEN }}"
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/workspace.xml
42 | .idea/tasks.xml
43 | .idea/gradle.xml
44 | .idea/assetWizardSettings.xml
45 | .idea/dictionaries
46 | .idea/libraries
47 | # Android Studio 3 in .gitignore file.
48 | .idea/caches
49 | .idea/modules.xml
50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
51 | .idea/navEditor.xml
52 |
53 | # Keystore files
54 | # Uncomment the following lines if you do not want to check your keystore files in.
55 | #*.jks
56 | #*.keystore
57 |
58 | # External native build folder generated in Android Studio 2.2 and later
59 | .externalNativeBuild
60 | .cxx/
61 |
62 | # Google Services (e.g. APIs or Firebase)
63 | # google-services.json
64 |
65 | # Freeline
66 | freeline.py
67 | freeline/
68 | freeline_project_description.json
69 |
70 | # fastlane
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 | fastlane/readme.md
76 |
77 | # Version control
78 | vcs.xml
79 |
80 | # lint
81 | lint/intermediates/
82 | lint/generated/
83 | lint/outputs/
84 | lint/tmp/
85 | # lint/reports/
86 | /.idea/
87 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "969552868617",
4 | "project_id": "calendarun-496ef",
5 | "storage_bucket": "calendarun-496ef.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:969552868617:android:0883d9aebfe785bf894fd8",
11 | "android_client_info": {
12 | "package_name": "com.drunkenboys.calendarun"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "969552868617-bd4bpbtqahmisq5e5jnpsgnrvpanuh4b.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyD9eXO5MQd50gOqu93bTdzPJAPThlP5nyk"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "969552868617-bd4bpbtqahmisq5e5jnpsgnrvpanuh4b.apps.googleusercontent.com",
31 | "client_type": 3
32 | }
33 | ]
34 | }
35 | }
36 | }
37 | ],
38 | "configuration_version": "1"
39 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/drunkenboys/calendarun/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.drunkenboys.calendarun", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/assets/default.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/assets/default.db
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/App.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun
2 |
3 | import android.app.Application
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.os.Build
7 | import androidx.core.content.getSystemService
8 | import com.drunkenboys.calendarun.receiver.ScheduleAlarmReceiver
9 | import dagger.hilt.android.HiltAndroidApp
10 |
11 | @HiltAndroidApp
12 | class App : Application() {
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | prepareNotificationChannel()
17 | }
18 |
19 | private fun prepareNotificationChannel() {
20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
21 | val notificationManager = applicationContext.getSystemService() ?: return
22 |
23 | notificationManager.getNotificationChannel(ScheduleAlarmReceiver.SCHEDULE_NOTIFICATION_CHANNEL_ID) ?: run {
24 | val notificationChannel = NotificationChannel(
25 | ScheduleAlarmReceiver.SCHEDULE_NOTIFICATION_CHANNEL_ID,
26 | ScheduleAlarmReceiver.SCHEDULE_NOTIFICATION_CHANNEL_NAME,
27 | NotificationManager.IMPORTANCE_HIGH
28 | )
29 |
30 | notificationManager.createNotificationChannel(notificationChannel)
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/SavedStateKeys.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun
2 |
3 | const val KEY_CALENDAR_ID = "calendarId"
4 | const val KEY_SCHEDULE_ID = "scheduleId"
5 | const val KEY_LOCAL_DATE = "localDate"
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendar/entity/Calendar.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendar.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import java.time.LocalDate
6 |
7 | @Entity(tableName = "Calendar")
8 | data class Calendar(
9 | @PrimaryKey(autoGenerate = true) val id: Long = 0,
10 | val name: String,
11 | val startDate: LocalDate,
12 | val endDate: LocalDate
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendar/local/CalendarDao.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendar.local
2 |
3 | import androidx.room.*
4 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | @Dao
8 | interface CalendarDao {
9 |
10 | @Insert
11 | suspend fun insertCalendar(calendar: Calendar): Long
12 |
13 | @Query("SELECT * FROM `calendar`")
14 | fun fetchAllCalendar(): Flow>
15 |
16 | @Query("SELECT * FROM `calendar` WHERE id != 1")
17 | fun fetchCustomCalendar(): Flow>
18 |
19 | @Query("SELECT * FROM `calendar` WHERE id == :id")
20 | suspend fun fetchCalendar(id: Long): Calendar
21 |
22 | @Delete
23 | suspend fun deleteCalendar(calendar: Calendar)
24 |
25 | @Update
26 | suspend fun updateCalendar(calendar: Calendar)
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendar/local/CalendarLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendar.local
2 |
3 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface CalendarLocalDataSource {
7 |
8 | suspend fun insertCalendar(calendar: Calendar): Long
9 |
10 | fun fetchAllCalendar(): Flow>
11 |
12 | fun fetchCustomCalendar(): Flow>
13 |
14 | suspend fun fetchCalendar(id: Long): Calendar
15 |
16 | suspend fun deleteCalendar(calendar: Calendar)
17 |
18 | suspend fun updateCalendar(calendar: Calendar)
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendar/local/CalendarLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendar.local
2 |
3 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import javax.inject.Inject
8 |
9 | class CalendarLocalDataSourceImpl @Inject constructor(
10 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
11 | private val calendarDao: CalendarDao
12 | ) : CalendarLocalDataSource {
13 |
14 | override suspend fun insertCalendar(calendar: Calendar) = withContext(dispatcher) {
15 | calendarDao.insertCalendar(calendar)
16 | }
17 |
18 | override fun fetchAllCalendar() = calendarDao.fetchAllCalendar()
19 |
20 | override fun fetchCustomCalendar() = calendarDao.fetchCustomCalendar()
21 |
22 | override suspend fun fetchCalendar(id: Long) = withContext(dispatcher) {
23 | calendarDao.fetchCalendar(id)
24 | }
25 |
26 | override suspend fun deleteCalendar(calendar: Calendar) {
27 | calendarDao.deleteCalendar(calendar)
28 | }
29 |
30 | override suspend fun updateCalendar(calendar: Calendar) {
31 | calendarDao.updateCalendar(calendar)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendartheme/entity/CalendarTheme.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendartheme.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "CalendarTheme")
7 | data class CalendarTheme(
8 | @PrimaryKey val id: Long = 1,
9 | val weekDayTextColor: Int,
10 | val holidayTextColor: Int,
11 | val saturdayTextColor: Int,
12 | val sundayTextColor: Int,
13 | val selectedFrameColor: Int,
14 | val backgroundColor: Int,
15 | val textSize: Float,
16 | val textAlign: Int,
17 | val languageType: LanguageType,
18 | val visibleScheduleCount: Int
19 | ) {
20 |
21 | enum class LanguageType(val description: String) {
22 | KOREAN("한글"),
23 | ENGLISH("영어")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendartheme/local/CalendarThemeDao.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendartheme.local
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import androidx.room.Update
6 | import com.drunkenboys.calendarun.data.calendartheme.entity.CalendarTheme
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | @Dao
10 | interface CalendarThemeDao {
11 |
12 | @Query("SELECT * FROM CalendarTheme where id = 1")
13 | fun fetchCalendarTheme(): Flow
14 |
15 | @Update
16 | suspend fun updateCalendarTheme(theme: CalendarTheme)
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendartheme/local/CalendarThemeLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendartheme.local
2 |
3 | import com.drunkenboys.calendarun.data.calendartheme.entity.CalendarTheme
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface CalendarThemeLocalDataSource {
7 |
8 | fun fetchCalendarTheme(): Flow
9 |
10 | suspend fun updateCalendarTheme(theme: CalendarTheme)
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/calendartheme/local/CalendarThemeLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendartheme.local
2 |
3 | import com.drunkenboys.calendarun.data.calendartheme.entity.CalendarTheme
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.withContext
7 | import javax.inject.Inject
8 |
9 | class CalendarThemeLocalDataSourceImpl @Inject constructor(
10 | private val calendarThemeDao: CalendarThemeDao,
11 | private val dispatcher: CoroutineDispatcher
12 | ) : CalendarThemeLocalDataSource {
13 |
14 | override fun fetchCalendarTheme(): Flow = calendarThemeDao.fetchCalendarTheme()
15 |
16 | override suspend fun updateCalendarTheme(theme: CalendarTheme) = withContext(dispatcher) {
17 | calendarThemeDao.updateCalendarTheme(theme)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/ResponseHolidayInfo.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | data class ResponseHolidayListInfo(
6 | val response: HolidayListResponse
7 | )
8 |
9 | data class HolidayListResponse(
10 | val body: HolidayListBody
11 | )
12 |
13 | data class HolidayListBody(
14 | val items: HolidayListItems
15 | )
16 |
17 | data class HolidayListItems(
18 | val item: List-
19 | )
20 |
21 | data class ResponseHolidayInfo(
22 | val response: HolidayResponse
23 | )
24 |
25 | data class HolidayResponse(
26 | val body: HolidayBody
27 | )
28 |
29 | data class HolidayBody(
30 | val items: HolidayItems
31 | )
32 |
33 | data class HolidayItems(
34 | val item: Item
35 | )
36 |
37 |
38 | data class Item(
39 | val dateName: String,
40 | @SerializedName("locdate")
41 | val localDate: Int
42 | )
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/entity/Holiday.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import java.time.LocalDate
6 |
7 | @Entity
8 | data class Holiday(
9 | @PrimaryKey(autoGenerate = true) val id: Long = 0,
10 | val name: String,
11 | val date: LocalDate,
12 | )
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/local/HolidayDao.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.local
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy.REPLACE
6 | import androidx.room.Query
7 | import com.drunkenboys.calendarun.data.holiday.entity.Holiday
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | @Dao
11 | interface HolidayDao {
12 |
13 | @Insert(onConflict = REPLACE)
14 | suspend fun insertHoliday(holiday: Holiday)
15 |
16 | @Query("SELECT * FROM `Holiday`")
17 | fun fetchAllHoliday(): Flow
>
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/local/HolidayLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.local
2 |
3 | import com.drunkenboys.calendarun.data.holiday.entity.Holiday
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface HolidayLocalDataSource {
7 |
8 | suspend fun insertHoliday(holiday: Holiday)
9 |
10 | fun fetchAllHoliday(): Flow>
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/local/HolidayLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.local
2 |
3 | import com.drunkenboys.calendarun.data.holiday.entity.Holiday
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.withContext
7 | import javax.inject.Inject
8 |
9 | class HolidayLocalDataSourceImpl @Inject constructor(
10 | private val holidayDao: HolidayDao,
11 | private val dispatcher: CoroutineDispatcher
12 | ) : HolidayLocalDataSource {
13 |
14 | override suspend fun insertHoliday(holiday: Holiday) = withContext(dispatcher) {
15 | holidayDao.insertHoliday(holiday)
16 | }
17 |
18 | override fun fetchAllHoliday(): Flow> = holidayDao.fetchAllHoliday()
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/remote/HolidayRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.remote
2 |
3 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayInfo
4 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayListInfo
5 |
6 | interface HolidayRemoteDataSource {
7 |
8 | suspend fun fetchHolidayListOnYear(year: String, pageNo: Int): ResponseHolidayListInfo
9 |
10 | suspend fun fetchHolidayListOnMonth(year: String, month: String): ResponseHolidayListInfo
11 |
12 | suspend fun fetchHolidayOnYear(year: String, pageNo: Int): ResponseHolidayInfo
13 |
14 | suspend fun fetchHolidayOnMonth(year: String, month: String): ResponseHolidayInfo
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/remote/HolidayRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.remote
2 |
3 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayInfo
4 | import javax.inject.Inject
5 |
6 | class HolidayRemoteDataSourceImpl @Inject constructor(
7 | private val holidayRemoteService: HolidayRemoteService
8 | ) : HolidayRemoteDataSource {
9 |
10 | override suspend fun fetchHolidayListOnYear(year: String, pageNo: Int) =
11 | holidayRemoteService.fetchHolidayListOnYear(year, pageNo)
12 |
13 | override suspend fun fetchHolidayListOnMonth(year: String, month: String) =
14 | holidayRemoteService.fetchHolidayListOnMonth(year, month)
15 |
16 | override suspend fun fetchHolidayOnYear(year: String, pageNo: Int): ResponseHolidayInfo =
17 | holidayRemoteService.fetchHolidayOnYear(year, pageNo)
18 |
19 | override suspend fun fetchHolidayOnMonth(year: String, month: String): ResponseHolidayInfo =
20 | holidayRemoteService.fetchHolidayOnMonth(year, month)
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/remote/HolidayRemoteService.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.remote
2 |
3 | import com.drunkenboys.calendarun.BuildConfig
4 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayInfo
5 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayListInfo
6 | import retrofit2.http.GET
7 | import retrofit2.http.Query
8 |
9 | interface HolidayRemoteService {
10 |
11 | @GET("/B090041/openapi/service/SpcdeInfoService/getRestDeInfo")
12 | suspend fun fetchHolidayListOnYear(
13 | @Query("solYear") year: String,
14 | @Query("pageNo") pageNo: Int,
15 | @Query("_type") type: String = "json",
16 | @Query("ServiceKey") serviceKey: String = BuildConfig.HOLIDAY_API_KEY
17 | ): ResponseHolidayListInfo
18 |
19 | @GET("/B090041/openapi/service/SpcdeInfoService/getRestDeInfo")
20 | suspend fun fetchHolidayListOnMonth(
21 | @Query("solYear") year: String,
22 | @Query("solMonth") month: String,
23 | @Query("_type") type: String = "json",
24 | @Query("ServiceKey") serviceKey: String = BuildConfig.HOLIDAY_API_KEY
25 | ): ResponseHolidayListInfo
26 |
27 | @GET("/B090041/openapi/service/SpcdeInfoService/getRestDeInfo")
28 | suspend fun fetchHolidayOnYear(
29 | @Query("solYear") year: String,
30 | @Query("pageNo") pageNo: Int,
31 | @Query("_type") type: String = "json",
32 | @Query("ServiceKey") serviceKey: String = BuildConfig.HOLIDAY_API_KEY
33 | ): ResponseHolidayInfo
34 |
35 | @GET("/B090041/openapi/service/SpcdeInfoService/getRestDeInfo")
36 | suspend fun fetchHolidayOnMonth(
37 | @Query("solYear") year: String,
38 | @Query("solMonth") month: String,
39 | @Query("_type") type: String = "json",
40 | @Query("ServiceKey") serviceKey: String = BuildConfig.HOLIDAY_API_KEY
41 | ): ResponseHolidayInfo
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/repository/HolidayRepository.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.repository
2 |
3 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayInfo
4 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayListInfo
5 | import com.drunkenboys.calendarun.data.holiday.entity.Holiday
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface HolidayRepository {
9 |
10 | suspend fun insertHoliday(holiday: Holiday)
11 |
12 | fun fetchAllHoliday(): Flow>
13 |
14 | suspend fun fetchHolidayListOnYear(year: String, pageNo: Int): ResponseHolidayListInfo
15 |
16 | suspend fun fetchHolidayListOnMonth(year: String, month: String): ResponseHolidayListInfo
17 |
18 | suspend fun fetchHolidayOnYear(year: String, pageNo: Int): ResponseHolidayInfo
19 |
20 | suspend fun fetchHolidayOnMonth(year: String, month: String): ResponseHolidayInfo
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/holiday/repository/HolidayRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.holiday.repository
2 |
3 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayInfo
4 | import com.drunkenboys.calendarun.data.holiday.ResponseHolidayListInfo
5 | import com.drunkenboys.calendarun.data.holiday.entity.Holiday
6 | import com.drunkenboys.calendarun.data.holiday.local.HolidayLocalDataSource
7 | import com.drunkenboys.calendarun.data.holiday.remote.HolidayRemoteDataSource
8 | import kotlinx.coroutines.flow.Flow
9 | import javax.inject.Inject
10 |
11 | class HolidayRepositoryImpl @Inject constructor(
12 | private val holidayLocalDataSource: HolidayLocalDataSource,
13 | private val holidayRemoteDataSource: HolidayRemoteDataSource
14 | ) : HolidayRepository {
15 |
16 | override suspend fun insertHoliday(holiday: Holiday) {
17 | holidayLocalDataSource.insertHoliday(holiday)
18 | }
19 |
20 | override fun fetchAllHoliday(): Flow> =
21 | holidayLocalDataSource.fetchAllHoliday()
22 |
23 | override suspend fun fetchHolidayListOnYear(year: String, pageNo: Int): ResponseHolidayListInfo =
24 | holidayRemoteDataSource.fetchHolidayListOnYear(year, pageNo)
25 |
26 | override suspend fun fetchHolidayListOnMonth(year: String, month: String): ResponseHolidayListInfo =
27 | holidayRemoteDataSource.fetchHolidayListOnMonth(year, month)
28 |
29 | override suspend fun fetchHolidayOnYear(year: String, pageNo: Int): ResponseHolidayInfo =
30 | holidayRemoteDataSource.fetchHolidayOnYear(year, pageNo)
31 |
32 | override suspend fun fetchHolidayOnMonth(year: String, month: String): ResponseHolidayInfo =
33 | holidayRemoteDataSource.fetchHolidayOnMonth(year, month)
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/room/Converters.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.room
2 |
3 | import androidx.room.TypeConverter
4 | import java.time.Instant
5 | import java.time.LocalDate
6 | import java.time.LocalDateTime
7 | import java.time.ZoneOffset
8 |
9 | class Converters {
10 |
11 | @TypeConverter
12 | fun fromDatestamp(value: Long): LocalDate = LocalDate.ofEpochDay(value)
13 |
14 | @TypeConverter
15 | fun dateToTimestamp(date: LocalDate): Long = date.toEpochDay()
16 |
17 | @TypeConverter
18 | fun fromDateTimestamp(value: Long): LocalDateTime = LocalDateTime.ofEpochSecond(value, 0, zoneOffset)
19 |
20 | @TypeConverter
21 | fun dateToDateTimestamp(time: LocalDateTime): Long = time.toEpochSecond(zoneOffset)
22 |
23 | companion object {
24 | private val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(Instant.now())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/schedule/entity/Schedule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.schedule.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.PrimaryKey
6 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
7 | import com.drunkenboys.calendarun.util.defaultZoneOffset
8 | import java.time.LocalDateTime
9 |
10 | @Entity(
11 | tableName = "Schedule",
12 | foreignKeys = [
13 | ForeignKey(
14 | entity = Calendar::class,
15 | parentColumns = ["id"],
16 | childColumns = ["calendarId"],
17 | onDelete = ForeignKey.CASCADE
18 | )
19 | ]
20 | )
21 | data class Schedule(
22 | @PrimaryKey(autoGenerate = true) val id: Long = 0,
23 | val calendarId: Long,
24 | val name: String,
25 | val startDate: LocalDateTime,
26 | val endDate: LocalDateTime,
27 | val notificationType: NotificationType,
28 | val memo: String,
29 | val color: Int
30 | ) {
31 |
32 | fun notificationDateTimeMillis(): Long {
33 | return when (notificationType) {
34 | NotificationType.NONE -> return 0
35 | NotificationType.TEN_MINUTES_AGO -> startDate.minusMinutes(10).toEpochSecond(defaultZoneOffset)
36 | NotificationType.A_HOUR_AGO -> startDate.minusHours(1).toEpochSecond(defaultZoneOffset)
37 | NotificationType.A_DAY_AGO -> startDate.minusDays(1).toEpochSecond(defaultZoneOffset)
38 | } * 1000
39 | }
40 |
41 | enum class NotificationType {
42 | NONE,
43 | TEN_MINUTES_AGO,
44 | A_HOUR_AGO,
45 | A_DAY_AGO
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/schedule/local/ScheduleDao.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.schedule.local
2 |
3 | import androidx.room.*
4 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
5 | import kotlinx.coroutines.flow.Flow
6 | import java.time.LocalDateTime
7 |
8 | @Dao
9 | interface ScheduleDao {
10 |
11 | @Insert
12 | suspend fun insertSchedule(schedule: Schedule): Long
13 |
14 | @Query("SELECT * FROM `schedule`")
15 | suspend fun fetchAllSchedule(): List
16 |
17 | @Query("SELECT * FROM `schedule` WHERE id == :id")
18 | suspend fun fetchSchedule(id: Long): Schedule
19 |
20 | @Query("SELECT * FROM `schedule` WHERE calendarId == :calendarId")
21 | fun fetchCalendarSchedules(calendarId: Long): Flow>
22 |
23 | @Update
24 | suspend fun updateSchedule(schedule: Schedule)
25 |
26 | @Delete
27 | suspend fun deleteSchedule(schedule: Schedule)
28 |
29 | @Query("SELECT * FROM `schedule` WHERE startDate > :time AND `name` LIKE '%' || :word || '%' ORDER BY startDate ASC LIMIT $SCHEDULE_PAGING_SIZE")
30 | suspend fun fetchMatchedScheduleAfter(word: String, time: Long): List
31 |
32 | @Query("SELECT * FROM (SELECT * FROM `schedule` WHERE startDate < :time AND `name` LIKE '%' || :word || '%' ORDER BY startDate DESC LIMIT $SCHEDULE_PAGING_SIZE) A ORDER BY A.startDate ASC")
33 | suspend fun fetchMatchedScheduleBefore(word: String, time: Long): List
34 |
35 | @Query("SELECT * FROM `schedule` WHERE startDate <= :endOfDate AND endDate >= :startOfDate ORDER BY startDate ASC")
36 | fun fetchDateSchedule(startOfDate: LocalDateTime, endOfDate: LocalDateTime): Flow>
37 |
38 | companion object {
39 |
40 | const val SCHEDULE_PAGING_SIZE = 30
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/schedule/local/ScheduleLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.schedule.local
2 |
3 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
4 | import kotlinx.coroutines.flow.Flow
5 | import java.time.LocalDateTime
6 |
7 | interface ScheduleLocalDataSource {
8 |
9 | suspend fun insertSchedule(schedule: Schedule): Long
10 |
11 | suspend fun fetchAllSchedule(): List
12 |
13 | suspend fun fetchSchedule(id: Long): Schedule
14 |
15 | fun fetchCalendarSchedules(calendarId: Long): Flow>
16 |
17 | suspend fun updateSchedule(schedule: Schedule)
18 |
19 | suspend fun deleteSchedule(schedule: Schedule)
20 |
21 | suspend fun fetchMatchedScheduleAfter(word: String, time: Long): List
22 |
23 | suspend fun fetchMatchedScheduleBefore(word: String, time: Long): List
24 |
25 | fun fetchDateSchedule(startOfDate: LocalDateTime, endOfDate: LocalDateTime): Flow>
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/schedule/local/ScheduleLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.schedule.local
2 |
3 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import java.time.LocalDateTime
8 | import javax.inject.Inject
9 |
10 | class ScheduleLocalDataSourceImpl @Inject constructor(
11 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
12 | private val scheduleDao: ScheduleDao
13 | ) : ScheduleLocalDataSource {
14 |
15 | override suspend fun insertSchedule(schedule: Schedule) = withContext(dispatcher) {
16 | scheduleDao.insertSchedule(schedule)
17 | }
18 |
19 | override suspend fun fetchAllSchedule() = withContext(dispatcher) {
20 | scheduleDao.fetchAllSchedule()
21 | }
22 |
23 | override fun fetchCalendarSchedules(calendarId: Long) = scheduleDao.fetchCalendarSchedules(calendarId)
24 |
25 | override suspend fun fetchSchedule(id: Long) = withContext(dispatcher) {
26 | scheduleDao.fetchSchedule(id)
27 | }
28 |
29 | override suspend fun updateSchedule(schedule: Schedule) = withContext(dispatcher) {
30 | scheduleDao.updateSchedule(schedule)
31 | }
32 |
33 | override suspend fun deleteSchedule(schedule: Schedule) = withContext(dispatcher) {
34 | scheduleDao.deleteSchedule(schedule)
35 | }
36 |
37 | override suspend fun fetchMatchedScheduleAfter(word: String, time: Long) = withContext(dispatcher) {
38 | scheduleDao.fetchMatchedScheduleAfter(word, time)
39 | }
40 |
41 | override suspend fun fetchMatchedScheduleBefore(word: String, time: Long) = withContext(dispatcher) {
42 | scheduleDao.fetchMatchedScheduleBefore(word, time)
43 | }
44 |
45 | override fun fetchDateSchedule(startOfDate: LocalDateTime, endOfDate: LocalDateTime) =
46 | scheduleDao.fetchDateSchedule(startOfDate, endOfDate)
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/slice/entity/Slice.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.slice.entity
2 |
3 | import androidx.room.Entity
4 | import androidx.room.ForeignKey
5 | import androidx.room.PrimaryKey
6 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
7 | import java.time.LocalDate
8 |
9 | @Entity(
10 | tableName = "Slice",
11 | foreignKeys = [
12 | ForeignKey(
13 | entity = Calendar::class,
14 | parentColumns = ["id"],
15 | childColumns = ["calendarId"],
16 | onDelete = ForeignKey.CASCADE,
17 | onUpdate = ForeignKey.CASCADE
18 | )
19 | ]
20 | )
21 | data class Slice(
22 | @PrimaryKey(autoGenerate = true) val id: Long = 0,
23 | val calendarId: Long,
24 | val name: String,
25 | val startDate: LocalDate,
26 | val endDate: LocalDate
27 | )
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/slice/local/SliceDao.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.slice.local
2 |
3 | import androidx.room.*
4 | import com.drunkenboys.calendarun.data.slice.entity.Slice
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | @Dao
8 | interface SliceDao {
9 |
10 | @Insert
11 | suspend fun insertSlice(slice: Slice): Long
12 |
13 | @Query("SELECT * FROM `Slice`")
14 | suspend fun fetchAllSlice(): List
15 |
16 | @Query("SELECT * FROM `Slice` WHERE id == :id")
17 | suspend fun fetchSlice(id: Long): Slice
18 |
19 | @Query("SELECT * FROM `Slice` WHERE calendarId == :calendarId ORDER BY startDate ASC")
20 | fun fetchCalendarSliceList(calendarId: Long): Flow>
21 |
22 | @Update
23 | suspend fun updateSlice(slice: Slice)
24 |
25 | @Query("DELETE FROM `Slice` WHERE calendarId == :calendarId")
26 | suspend fun deleteSliceList(calendarId: Long)
27 |
28 | @Delete
29 | suspend fun deleteSlice(slice: Slice)
30 |
31 | @Query("DELETE FROM `Slice` WHERE id == :id")
32 | suspend fun deleteSliceById(id: Long)
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/slice/local/SliceLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.slice.local
2 |
3 | import com.drunkenboys.calendarun.data.slice.entity.Slice
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SliceLocalDataSource {
7 |
8 | suspend fun insertSlice(slice: Slice): Long
9 |
10 | suspend fun fetchAllSlice(): List
11 |
12 | suspend fun fetchSlice(id: Long): Slice
13 |
14 | fun fetchCalendarSliceList(calendarId: Long): Flow>
15 |
16 | suspend fun updateSlice(slice: Slice)
17 |
18 | suspend fun deleteSliceList(calendarId: Long)
19 |
20 | suspend fun deleteSlice(slice: Slice)
21 |
22 | suspend fun deleteSliceById(id: Long)
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/data/slice/local/SliceLocalDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.slice.local
2 |
3 | import com.drunkenboys.calendarun.data.slice.entity.Slice
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import javax.inject.Inject
8 |
9 | class SliceLocalDataSourceImpl @Inject constructor(
10 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
11 | private val sliceDao: SliceDao
12 | ) : SliceLocalDataSource {
13 |
14 | override suspend fun insertSlice(slice: Slice) = withContext(dispatcher) {
15 | sliceDao.insertSlice(slice)
16 | }
17 |
18 | override suspend fun fetchAllSlice() = withContext(dispatcher) {
19 | sliceDao.fetchAllSlice()
20 | }
21 |
22 | override suspend fun fetchSlice(id: Long) = withContext(dispatcher) {
23 | sliceDao.fetchSlice(id)
24 | }
25 |
26 | override fun fetchCalendarSliceList(calendarId: Long) = sliceDao.fetchCalendarSliceList(calendarId)
27 |
28 | override suspend fun updateSlice(slice: Slice) {
29 | sliceDao.updateSlice(slice)
30 | }
31 |
32 | override suspend fun deleteSliceList(calendarId: Long) {
33 | sliceDao.deleteSliceList(calendarId)
34 | }
35 |
36 | override suspend fun deleteSlice(slice: Slice) {
37 | sliceDao.deleteSlice(slice)
38 | }
39 |
40 | override suspend fun deleteSliceById(id: Long) {
41 | sliceDao.deleteSliceById(id)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/di/CoroutineModule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.di
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 | import kotlinx.coroutines.Dispatchers
8 |
9 | @InstallIn(SingletonComponent::class)
10 | @Module
11 | object CoroutineModule {
12 |
13 | @Provides
14 | fun provideIODispatcher() = Dispatchers.IO
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/di/DataSourceModule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.di
2 |
3 | import com.drunkenboys.calendarun.data.calendar.local.CalendarLocalDataSource
4 | import com.drunkenboys.calendarun.data.calendar.local.CalendarLocalDataSourceImpl
5 | import com.drunkenboys.calendarun.data.calendartheme.local.CalendarThemeLocalDataSource
6 | import com.drunkenboys.calendarun.data.calendartheme.local.CalendarThemeLocalDataSourceImpl
7 | import com.drunkenboys.calendarun.data.holiday.local.HolidayLocalDataSource
8 | import com.drunkenboys.calendarun.data.holiday.local.HolidayLocalDataSourceImpl
9 | import com.drunkenboys.calendarun.data.holiday.remote.HolidayRemoteDataSource
10 | import com.drunkenboys.calendarun.data.holiday.remote.HolidayRemoteDataSourceImpl
11 | import com.drunkenboys.calendarun.data.schedule.local.ScheduleLocalDataSource
12 | import com.drunkenboys.calendarun.data.schedule.local.ScheduleLocalDataSourceImpl
13 | import com.drunkenboys.calendarun.data.slice.local.SliceLocalDataSource
14 | import com.drunkenboys.calendarun.data.slice.local.SliceLocalDataSourceImpl
15 | import dagger.Binds
16 | import dagger.Module
17 | import dagger.hilt.InstallIn
18 | import dagger.hilt.components.SingletonComponent
19 |
20 | @InstallIn(SingletonComponent::class)
21 | @Module
22 | abstract class DataSourceModule {
23 |
24 | @Binds
25 | abstract fun bindCalendarDataSource(dataSource: CalendarLocalDataSourceImpl): CalendarLocalDataSource
26 |
27 | @Binds
28 | abstract fun bindSliceDataSource(dataSource: SliceLocalDataSourceImpl): SliceLocalDataSource
29 |
30 | @Binds
31 | abstract fun bindScheduleDataSource(dataSource: ScheduleLocalDataSourceImpl): ScheduleLocalDataSource
32 |
33 | @Binds
34 | abstract fun bindCalendarThemeDataSource(dataSource: CalendarThemeLocalDataSourceImpl): CalendarThemeLocalDataSource
35 |
36 | @Binds
37 | abstract fun bindHolidayLocalDataSource(dataSource: HolidayLocalDataSourceImpl): HolidayLocalDataSource
38 |
39 | @Binds
40 | abstract fun bindHolidayRemoteDataSource(dataSource: HolidayRemoteDataSourceImpl): HolidayRemoteDataSource
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/di/HolidayRetrofitServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.di
2 |
3 | import com.drunkenboys.calendarun.data.holiday.remote.HolidayRemoteService
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import retrofit2.Retrofit
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | object HolidayRetrofitServiceModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideHolidayService(retrofit: Retrofit): HolidayRemoteService =
18 | retrofit.create(HolidayRemoteService::class.java)
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/di/LocalDatabaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.di
2 |
3 | import android.content.Context
4 | import androidx.room.Room
5 | import com.drunkenboys.calendarun.data.room.Database
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.android.qualifiers.ApplicationContext
10 | import dagger.hilt.components.SingletonComponent
11 | import javax.inject.Singleton
12 |
13 | @InstallIn(SingletonComponent::class)
14 | @Module
15 | object LocalDatabaseModule {
16 |
17 | @Provides
18 | @Singleton
19 | fun provideDatabase(@ApplicationContext appContext: Context) = Room.databaseBuilder(
20 | appContext,
21 | Database::class.java,
22 | "AppDatabase.db"
23 | )
24 | .createFromAsset("default.db")
25 | .addMigrations(Database.MIGRATION_2_3, Database.MIGRATION_3_4, Database.MIGRATION_4_5, Database.MIGRATION_5_6)
26 | .build()
27 |
28 | @Provides
29 | fun provideCalendarDao(database: Database) = database.calendarDao()
30 |
31 | @Provides
32 | fun provideSliceDao(database: Database) = database.sliceDao()
33 |
34 | @Provides
35 | fun provideScheduleDao(database: Database) = database.scheduleDao()
36 |
37 | @Provides
38 | fun provideCalendarThemeDao(database: Database) = database.calendarThemeDao()
39 |
40 | @Provides
41 | fun provideHolidayDao(database: Database) = database.holidayDao()
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.di
2 |
3 | import com.drunkenboys.calendarun.data.holiday.repository.HolidayRepository
4 | import com.drunkenboys.calendarun.data.holiday.repository.HolidayRepositoryImpl
5 | import dagger.Binds
6 | import dagger.Module
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 |
10 | @InstallIn(SingletonComponent::class)
11 | @Module
12 | abstract class RepositoryModule {
13 |
14 | @Binds
15 | abstract fun bindHolidayRepository(repository: HolidayRepositoryImpl): HolidayRepository
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/di/RetrofitModule.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.di
2 |
3 | import com.google.gson.GsonBuilder
4 | import dagger.Module
5 | import dagger.Provides
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 | import retrofit2.Retrofit
9 | import retrofit2.converter.gson.GsonConverterFactory
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | @InstallIn(SingletonComponent::class)
14 | object RetrofitModule {
15 |
16 | private const val HOLIDAY_URL = "http://apis.data.go.kr"
17 |
18 | private val gson = GsonBuilder()
19 | .setLenient()
20 | .create()
21 |
22 | @Provides
23 | @Singleton
24 | fun provideHolidayRetrofit(): Retrofit =
25 | Retrofit.Builder()
26 | .baseUrl(HOLIDAY_URL)
27 | .addConverterFactory(GsonConverterFactory.create(gson))
28 | .build()
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/receiver/BootReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.receiver
2 |
3 | import android.app.AlarmManager
4 | import android.content.BroadcastReceiver
5 | import android.content.Context
6 | import android.content.Intent
7 | import androidx.core.content.getSystemService
8 | import com.drunkenboys.calendarun.data.calendar.local.CalendarLocalDataSource
9 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
10 | import com.drunkenboys.calendarun.data.schedule.local.ScheduleLocalDataSource
11 | import dagger.hilt.android.AndroidEntryPoint
12 | import kotlinx.coroutines.CoroutineScope
13 | import kotlinx.coroutines.Job
14 | import kotlinx.coroutines.flow.firstOrNull
15 | import kotlinx.coroutines.launch
16 | import java.time.LocalDateTime
17 | import javax.inject.Inject
18 |
19 | @AndroidEntryPoint
20 | class BootReceiver : BroadcastReceiver() {
21 |
22 | @Inject lateinit var calendarDataSource: CalendarLocalDataSource
23 | @Inject lateinit var scheduleDataSource: ScheduleLocalDataSource
24 |
25 | private val job = Job()
26 | private val coroutineScope = CoroutineScope(job)
27 |
28 | private val today = LocalDateTime.now()
29 |
30 | private lateinit var alarmManager: AlarmManager
31 |
32 | override fun onReceive(context: Context, intent: Intent) {
33 | if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
34 | alarmManager = context.getSystemService() ?: return
35 | setNotifications(context)
36 | } else {
37 | return
38 | }
39 | }
40 |
41 | private fun setNotifications(context: Context) {
42 | coroutineScope.launch {
43 | val calendarMap = calendarDataSource.fetchAllCalendar().firstOrNull()
44 | ?.associate { it.id to it.name } ?: return@launch
45 |
46 | scheduleDataSource.fetchAllSchedule()
47 | .forEach { schedule ->
48 | setAlarmIfScheduleInFuture(
49 | schedule,
50 | context,
51 | calendarMap.getOrDefault(schedule.id, "null")
52 | )
53 | }
54 | }
55 | }
56 |
57 | private fun setAlarmIfScheduleInFuture(schedule: Schedule, context: Context, calendarName: String) {
58 | if (schedule.startDate > today) {
59 | val triggerAtMillis = schedule.notificationDateTimeMillis()
60 | alarmManager.set(
61 | AlarmManager.RTC_WAKEUP,
62 | triggerAtMillis,
63 | ScheduleAlarmReceiver.createPendingIntent(context, schedule, calendarName)
64 | )
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/appinfo/AppInfoFragment.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.appinfo
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.navigation.ui.setupWithNavController
6 | import com.drunkenboys.calendarun.R
7 | import com.drunkenboys.calendarun.databinding.FragmentAppInfoBinding
8 | import com.drunkenboys.calendarun.ui.base.BaseFragment
9 |
10 | class AppInfoFragment : BaseFragment(R.layout.fragment_app_info) {
11 |
12 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
13 | super.onViewCreated(view, savedInstanceState)
14 |
15 | setupToolbar()
16 | }
17 |
18 | private fun setupToolbar() {
19 | binding.toolbarAppInfo.setupWithNavController(navController)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/appwidget/CalendaRunRemoteViewsFactory.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.appwidget
2 |
3 | import android.content.Context
4 | import android.widget.RemoteViews
5 | import android.widget.RemoteViewsService
6 | import com.drunkenboys.calendarun.R
7 | import com.drunkenboys.calendarun.data.schedule.local.ScheduleLocalDataSource
8 | import com.drunkenboys.calendarun.util.getEndOfDate
9 | import com.drunkenboys.calendarun.util.getStartOfDate
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.SharingStarted
13 | import kotlinx.coroutines.flow.collectLatest
14 | import kotlinx.coroutines.flow.stateIn
15 | import kotlinx.coroutines.launch
16 |
17 | class CalendaRunRemoteViewsFactory constructor(
18 | private val context: Context,
19 | private val scheduleDataSource: ScheduleLocalDataSource
20 | ) : RemoteViewsService.RemoteViewsFactory {
21 |
22 | private val coroutineScope = CoroutineScope(Dispatchers.IO)
23 | private var scheduleList = scheduleDataSource.fetchDateSchedule(
24 | getStartOfDate(), getEndOfDate()
25 | ).stateIn(coroutineScope, SharingStarted.Eagerly, emptyList())
26 |
27 | override fun onCreate() {
28 | coroutineScope.launch {
29 | scheduleList.collectLatest {
30 | CalendaRunAppWidget.sendUpdateBroadcast(context)
31 | }
32 | }
33 | }
34 |
35 | override fun onDataSetChanged() {}
36 |
37 | override fun onDestroy() {}
38 |
39 | override fun getCount() = scheduleList.value.size
40 |
41 | override fun getViewAt(position: Int): RemoteViews {
42 | val widget = RemoteViews(context.packageName, R.layout.item_app_widget_schedule).apply {
43 | setTextViewText(R.id.tv_appWidget_schedule_name, scheduleList.value[position].name)
44 | setTextViewText(R.id.tv_appWidget_schedule_memo, scheduleList.value[position].memo)
45 | }
46 |
47 | return widget
48 | }
49 |
50 | override fun getLoadingView(): RemoteViews? = null
51 |
52 | override fun getViewTypeCount() = 1
53 |
54 | override fun getItemId(position: Int) = 0L
55 |
56 | override fun hasStableIds() = false
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/appwidget/CalendaRunRemoteViewsService.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.appwidget
2 |
3 | import android.content.Intent
4 | import android.widget.RemoteViewsService
5 | import com.drunkenboys.calendarun.data.schedule.local.ScheduleLocalDataSource
6 | import dagger.hilt.android.AndroidEntryPoint
7 | import javax.inject.Inject
8 |
9 | @AndroidEntryPoint
10 | class CalendaRunRemoteViewsService : RemoteViewsService() {
11 |
12 | @Inject
13 | lateinit var scheduleDataSource: ScheduleLocalDataSource
14 |
15 | override fun onGetViewFactory(p0: Intent?) = CalendaRunRemoteViewsFactory(this.applicationContext, scheduleDataSource)
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.databinding.DataBindingUtil
7 | import androidx.databinding.ViewDataBinding
8 | import androidx.viewbinding.ViewBinding
9 |
10 | // for view binding
11 | open class BaseViewActivity(private val inflate: ((LayoutInflater) -> T)?) : AppCompatActivity() {
12 |
13 | lateinit var binding: T
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | inflate?.let {
18 | binding = it.invoke(layoutInflater)
19 | setContentView(binding.root)
20 | }
21 | }
22 | }
23 |
24 | // for data binding
25 | open class BaseActivity(private val layoutId: Int) : BaseViewActivity(null) {
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | binding = DataBindingUtil.setContentView(this, layoutId)
30 | binding.lifecycleOwner = this
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.databinding.ViewDataBinding
9 | import androidx.fragment.app.Fragment
10 | import androidx.navigation.fragment.findNavController
11 | import androidx.viewbinding.ViewBinding
12 |
13 | // for view binding
14 | open class BaseViewFragment(private val inflate: ((LayoutInflater, ViewGroup?, Boolean) -> T)? = null) : Fragment() {
15 |
16 | var _binding: T? = null
17 | val binding get() = _binding ?: throw IllegalStateException(ERROR_BINDING_INITIALIZE)
18 |
19 | val navController by lazy { findNavController() }
20 |
21 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
22 | return inflate?.let {
23 | _binding = it.invoke(inflater, container, false)
24 | binding.root
25 | } ?: super.onCreateView(inflater, container, savedInstanceState)
26 | }
27 |
28 | override fun onDestroyView() {
29 | super.onDestroyView()
30 | _binding = null
31 | }
32 |
33 | companion object {
34 |
35 | private const val ERROR_BINDING_INITIALIZE = "binding 초기화 실패"
36 | }
37 | }
38 |
39 | // for data binding
40 | open class BaseFragment(private val layoutId: Int) : BaseViewFragment() {
41 |
42 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
43 | _binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
44 | return binding.root
45 | }
46 |
47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 | binding.lifecycleOwner = viewLifecycleOwner
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/base/BaseViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.base
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.databinding.DataBindingUtil
6 | import androidx.databinding.ViewDataBinding
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | class BaseViewHolder(val binding: VDB) : RecyclerView.ViewHolder(binding.root) {
10 |
11 | constructor(parent: ViewGroup, layoutId: Int) : this(
12 | DataBindingUtil.inflate(LayoutInflater.from(parent.context), layoutId, parent, false)
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/dayschedule/DayScheduleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.dayschedule
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.ListAdapter
5 | import com.drunkenboys.calendarun.R
6 | import com.drunkenboys.calendarun.databinding.ItemScheduleBinding
7 | import com.drunkenboys.calendarun.ui.base.BaseViewHolder
8 | import com.drunkenboys.calendarun.ui.searchschedule.model.ScheduleItem
9 | import java.time.LocalDate
10 | import java.time.LocalDateTime
11 | import java.time.LocalTime
12 |
13 | class DayScheduleAdapter(private val localDate: LocalDate) :
14 | ListAdapter>(ScheduleItem.diffUtil) {
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder =
17 | BaseViewHolder(parent, R.layout.item_schedule)
18 |
19 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
20 | holder.binding.item = currentList[position]
21 | holder.binding.time = LocalDateTime.of(localDate, LocalTime.MIN)
22 | holder.binding.executePendingBindings()
23 | holder.binding.root.post {
24 | val textLine = holder.binding.tvScheduleTime.lineCount
25 | if (textLine == 2) {
26 | val text = holder.binding.tvScheduleTime.text
27 | holder.binding.tvScheduleTime.text = text.replace("~ ".toRegex(), "~\n")
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/dayschedule/DayScheduleViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.dayschedule
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.drunkenboys.calendarun.KEY_CALENDAR_ID
7 | import com.drunkenboys.calendarun.KEY_LOCAL_DATE
8 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
9 | import com.drunkenboys.calendarun.data.schedule.local.ScheduleLocalDataSource
10 | import com.drunkenboys.calendarun.ui.searchschedule.model.ScheduleItem
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import kotlinx.coroutines.flow.*
13 | import kotlinx.coroutines.launch
14 | import java.time.LocalDate
15 | import java.time.format.DateTimeFormatter
16 | import javax.inject.Inject
17 |
18 | @HiltViewModel
19 | class DayScheduleViewModel @Inject constructor(
20 | savedStateHandle: SavedStateHandle,
21 | scheduleDataSource: ScheduleLocalDataSource
22 | ) : ViewModel() {
23 |
24 | private val calendarId = savedStateHandle[KEY_CALENDAR_ID] ?: 0L
25 | private val localDate = savedStateHandle.get(KEY_LOCAL_DATE)
26 | ?.let { LocalDate.parse(it) }
27 |
28 | val dateString = localDate?.format(DateTimeFormatter.ofPattern("M월 d일"))
29 |
30 | val listItem = scheduleDataSource.fetchCalendarSchedules(calendarId)
31 | .map { scheduleList ->
32 | scheduleList.filter { localDate in it.startDate.toLocalDate()..it.endDate.toLocalDate() }
33 | .map { schedule -> ScheduleItem(schedule) { emitScheduleClickEvent(schedule) } }
34 | .sortedBy { dateScheduleItem -> dateScheduleItem.schedule.startDate }
35 | }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
36 |
37 | private val _scheduleClickEvent = MutableSharedFlow()
38 | val scheduleClickEvent: SharedFlow = _scheduleClickEvent
39 |
40 | private fun emitScheduleClickEvent(schedule: Schedule) {
41 | viewModelScope.launch {
42 | _scheduleClickEvent.emit(schedule)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/maincalendar/LoadingDialog.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.maincalendar
2 |
3 | import android.animation.Animator
4 | import android.animation.AnimatorListenerAdapter
5 | import android.app.Dialog
6 | import android.graphics.Color
7 | import android.graphics.drawable.ColorDrawable
8 | import android.os.Bundle
9 | import android.view.LayoutInflater
10 | import android.view.View
11 | import android.view.ViewGroup
12 | import androidx.fragment.app.DialogFragment
13 | import com.airbnb.lottie.LottieDrawable
14 | import com.drunkenboys.calendarun.databinding.DialogLoadingBinding
15 |
16 | /*
17 | * @params isWaiting
18 | * tre : 따로 Dismiss()를 호출 할 때까지 무한 반복
19 | * false : 60frame 실행 후 자동 종료
20 | * */
21 | class LoadingDialog(private var isWaiting: Boolean = false) : DialogFragment() {
22 |
23 | private var _binding: DialogLoadingBinding? = null
24 | private val binding get() = _binding!!
25 |
26 |
27 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
28 | return super.onCreateDialog(savedInstanceState).apply {
29 | setCancelable(false)
30 | setCanceledOnTouchOutside(false)
31 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
32 | window?.setDimAmount(0.3f)
33 | }
34 | }
35 |
36 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
37 | _binding = DialogLoadingBinding.inflate(layoutInflater, container, false)
38 | return binding.root
39 | }
40 |
41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
42 | super.onViewCreated(view, savedInstanceState)
43 | binding.lottieLoading.setAnimation(LOTTIE_ANIMATION_PATH)
44 | binding.lottieLoading.speed = 3.5f
45 | if (!isWaiting) {
46 | binding.lottieLoading.setMaxFrame(60)
47 | } else {
48 | binding.lottieLoading.repeatCount = LottieDrawable.INFINITE
49 | }
50 | binding.lottieLoading.playAnimation()
51 | binding.lottieLoading.addAnimatorListener(object : AnimatorListenerAdapter() {
52 | override fun onAnimationEnd(animation: Animator?) {
53 | if (!isWaiting) {
54 | dismiss()
55 | }
56 | }
57 | })
58 | }
59 |
60 | override fun onDestroyView() {
61 | super.onDestroyView()
62 | _binding = null
63 | }
64 |
65 | companion object {
66 | private const val LOTTIE_ANIMATION_PATH = "switching_calendar.json"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/managecalendar/DeleteCalendarDialog.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.managecalendar
2 |
3 | import android.app.AlertDialog
4 | import android.os.Bundle
5 | import androidx.fragment.app.DialogFragment
6 | import androidx.navigation.fragment.findNavController
7 | import androidx.navigation.navGraphViewModels
8 | import com.drunkenboys.calendarun.R
9 | import dagger.hilt.android.AndroidEntryPoint
10 |
11 | @AndroidEntryPoint
12 | class DeleteCalendarDialog : DialogFragment() {
13 |
14 | private val manageCalendarViewModel: ManageCalendarViewModel
15 | by navGraphViewModels(R.id.manageCalendarFragment) { defaultViewModelProviderFactory }
16 |
17 | override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog = AlertDialog.Builder(requireContext())
18 | .setTitle(getString(R.string.deleteCalendar_title))
19 | .setMessage(getString(R.string.deleteCalendar_message))
20 | .setPositiveButton(getString(R.string.delete)) { _, _ ->
21 | manageCalendarViewModel.emitDeleteCalendarEvent()
22 | findNavController().navigateUp()
23 | }
24 | .setNegativeButton(getString(R.string.cancel)) { _, _ -> }
25 | .create()
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/managecalendar/ManageCalendarAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.managecalendar
2 |
3 | import android.view.ViewGroup
4 | import android.widget.CompoundButton
5 | import androidx.recyclerview.widget.ListAdapter
6 | import com.drunkenboys.calendarun.BR
7 | import com.drunkenboys.calendarun.R
8 | import com.drunkenboys.calendarun.databinding.ItemCalendarBinding
9 | import com.drunkenboys.calendarun.ui.base.BaseViewHolder
10 | import com.drunkenboys.calendarun.ui.managecalendar.model.CalendarItem
11 | import kotlinx.coroutines.flow.MutableStateFlow
12 | import kotlinx.coroutines.flow.StateFlow
13 |
14 | class ManageCalendarAdapter(val onClick: (CalendarItem) -> Unit) :
15 | ListAdapter>(CalendarItem.diffUtil) {
16 |
17 | private val _checkedCalendarNum = MutableStateFlow(0)
18 | val checkedCalendarNum: StateFlow = _checkedCalendarNum
19 |
20 | val onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { button, isChecked ->
21 | if (isChecked) {
22 | _checkedCalendarNum.value++
23 | } else {
24 | _checkedCalendarNum.value--
25 | }
26 | }
27 |
28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder =
29 | BaseViewHolder(parent, R.layout.item_calendar)
30 |
31 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
32 | with(holder.binding) {
33 | setVariable(BR.item, currentList[position])
34 | setVariable(BR.adapter, this@ManageCalendarAdapter)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/managecalendar/ManageCalendarViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.managecalendar
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.drunkenboys.calendarun.data.calendar.local.CalendarLocalDataSource
6 | import com.drunkenboys.calendarun.ui.managecalendar.model.CalendarItem
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.*
9 | import kotlinx.coroutines.launch
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class ManageCalendarViewModel @Inject constructor(
14 | private val calendarLocalDataSource: CalendarLocalDataSource
15 | ) : ViewModel() {
16 |
17 | val calendarItemList = calendarLocalDataSource.fetchCustomCalendar()
18 | .map { calendarList ->
19 | calendarList.map { calendar -> CalendarItem.from(calendar) }
20 | }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
21 |
22 | private val _deleteCalendarEvent = MutableSharedFlow()
23 | val deleteCalendarEvent: SharedFlow = _deleteCalendarEvent
24 |
25 | private val _openDeleteDialogEvent = MutableSharedFlow()
26 | val openDeleteDialogEvent: SharedFlow = _openDeleteDialogEvent
27 |
28 | fun deleteCalendarItem(currentCalendarItemList: List) {
29 | viewModelScope.launch {
30 | currentCalendarItemList
31 | .filter { calendarItem -> calendarItem.check }
32 | .forEach { calendarItem ->
33 | calendarLocalDataSource.deleteCalendar(
34 | calendarItem.toCalendar()
35 | )
36 | }
37 | }
38 | }
39 |
40 | fun emitDeleteCalendarEvent() {
41 | viewModelScope.launch {
42 | _deleteCalendarEvent.emit(Unit)
43 | }
44 | }
45 |
46 | fun emitOpenDeleteDialogEvent(id: Int) {
47 | viewModelScope.launch {
48 | _openDeleteDialogEvent.emit(id)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/managecalendar/model/CalendarItem.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.managecalendar.model
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
5 | import java.time.LocalDate
6 |
7 | data class CalendarItem(
8 | val id: Long,
9 | val name: String,
10 | val startDate: LocalDate,
11 | val endDate: LocalDate,
12 | var check: Boolean = false
13 | ) {
14 |
15 | fun toCalendar() = Calendar(
16 | id = id,
17 | name = name,
18 | startDate = startDate,
19 | endDate = endDate
20 | )
21 |
22 | companion object {
23 |
24 | val diffUtil by lazy {
25 | object : DiffUtil.ItemCallback() {
26 | override fun areItemsTheSame(oldItem: CalendarItem, newItem: CalendarItem) = oldItem.id == newItem.id
27 |
28 | override fun areContentsTheSame(oldItem: CalendarItem, newItem: CalendarItem) = oldItem == newItem
29 | }
30 | }
31 |
32 | fun from(calendar: Calendar) = CalendarItem(
33 | id = calendar.id,
34 | name = calendar.name,
35 | startDate = calendar.startDate,
36 | endDate = calendar.endDate
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/savecalendar/SaveCalendarAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.savecalendar
2 |
3 | import android.view.ViewGroup
4 | import androidx.lifecycle.LifecycleOwner
5 | import androidx.lifecycle.lifecycleScope
6 | import androidx.recyclerview.widget.ListAdapter
7 | import com.drunkenboys.calendarun.BR
8 | import com.drunkenboys.calendarun.R
9 | import com.drunkenboys.calendarun.databinding.ItemSliceBinding
10 | import com.drunkenboys.calendarun.ui.base.BaseViewHolder
11 | import com.drunkenboys.calendarun.ui.savecalendar.model.SliceItem
12 | import com.drunkenboys.calendarun.view.ErrorGuideEditText
13 | import com.drunkenboys.calendarun.view.ErrorGuideTextView
14 | import kotlinx.coroutines.flow.collectLatest
15 | import kotlinx.coroutines.launch
16 |
17 | class SaveCalendarAdapter(
18 | private val viewLifecycleOwner: LifecycleOwner,
19 | val onClick: (SliceItem) -> Unit
20 | ) : ListAdapter>(SliceItem.diffUtil) {
21 |
22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder =
23 | BaseViewHolder(parent, R.layout.item_slice)
24 |
25 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
26 | with(holder.binding) {
27 | setVariable(BR.item, currentList[position])
28 | setVariable(BR.adapter, this@SaveCalendarAdapter)
29 | lifecycleOwner = viewLifecycleOwner
30 |
31 | collectNameBlank(currentList[position], holder.binding.etSliceName)
32 | collectStartBlank(currentList[position], holder.binding.tvSliceStartDatePicker)
33 | collectEndBlank(currentList[position], holder.binding.tvSliceEndDatePicker)
34 | }
35 | }
36 |
37 | private fun collectNameBlank(item: SliceItem, view: ErrorGuideEditText) {
38 | viewLifecycleOwner.lifecycleScope.launch {
39 | item.isNameBlank.collectLatest {
40 | view.isError = true
41 | }
42 |
43 | }
44 | }
45 |
46 | private fun collectStartBlank(item: SliceItem, view: ErrorGuideTextView) {
47 | viewLifecycleOwner.lifecycleScope.launch {
48 | item.isStartDateBlank.collectLatest {
49 | view.isError = true
50 | }
51 | }
52 | }
53 |
54 | private fun collectEndBlank(item: SliceItem, view: ErrorGuideTextView) {
55 | viewLifecycleOwner.lifecycleScope.launch {
56 | item.isEndDateBlank.collectLatest {
57 | view.isError = true
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/savecalendar/model/SliceItem.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.savecalendar.model
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import kotlinx.coroutines.flow.MutableSharedFlow
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import java.time.LocalDate
7 |
8 | data class SliceItem(
9 | val id: Long = 0L,
10 | val name: MutableStateFlow = MutableStateFlow(""),
11 | val startDate: MutableStateFlow = MutableStateFlow(null),
12 | val endDate: MutableStateFlow = MutableStateFlow(null),
13 | var check: Boolean = false,
14 | val isNameBlank: MutableSharedFlow = MutableSharedFlow(),
15 | val isStartDateBlank: MutableSharedFlow = MutableSharedFlow(),
16 | val isEndDateBlank: MutableSharedFlow = MutableSharedFlow()
17 | ) : Comparable {
18 |
19 | override fun compareTo(other: SliceItem): Int =
20 | compareValuesBy(this, other) { slice -> slice.startDate.value }
21 |
22 | companion object {
23 |
24 | val diffUtil by lazy {
25 | object : DiffUtil.ItemCallback() {
26 | override fun areItemsTheSame(oldItem: SliceItem, newItem: SliceItem) =
27 | oldItem.startDate == newItem.startDate
28 |
29 | override fun areContentsTheSame(oldItem: SliceItem, newItem: SliceItem) =
30 | oldItem == newItem
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/saveschedule/DeleteScheduleDialog.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.saveschedule
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AlertDialog
5 | import androidx.fragment.app.DialogFragment
6 | import androidx.navigation.fragment.findNavController
7 | import androidx.navigation.navGraphViewModels
8 | import com.drunkenboys.calendarun.R
9 | import com.google.android.material.color.MaterialColors
10 | import dagger.hilt.android.AndroidEntryPoint
11 |
12 | @AndroidEntryPoint
13 | class DeleteScheduleDialog : DialogFragment() {
14 |
15 | private val saveScheduleViewModel: SaveScheduleViewModel
16 | by navGraphViewModels(R.id.saveScheduleFragment) { defaultViewModelProviderFactory }
17 |
18 | override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog = AlertDialog.Builder(requireContext())
19 | .setTitle(R.string.deleteScheduleDialog_title)
20 | .setMessage(R.string.deleteScheduleDialog_message)
21 | .setPositiveButton(R.string.delete) { _, _ ->
22 | saveScheduleViewModel.deleteSchedule()
23 | findNavController().navigateUp()
24 | }
25 | .setNegativeButton(R.string.cancel) { _, _ -> }
26 | .create()
27 |
28 | override fun onResume() {
29 | super.onResume()
30 |
31 | val dialog = (dialog as? AlertDialog) ?: return
32 | val textColor = MaterialColors.getColor(requireContext(), R.attr.colorOnSurface, "")
33 | dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(textColor)
34 | dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(textColor)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/saveschedule/model/BehaviorType.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.saveschedule.model
2 |
3 | enum class BehaviorType {
4 | INSERT, UPDATE
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/saveschedule/model/DateType.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.saveschedule.model
2 |
3 | enum class DateType {
4 | START,
5 | END
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/searchschedule/SearchScheduleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.searchschedule
2 |
3 | import android.view.ViewGroup
4 | import androidx.databinding.ViewDataBinding
5 | import androidx.recyclerview.widget.ListAdapter
6 | import com.drunkenboys.calendarun.BR
7 | import com.drunkenboys.calendarun.R
8 | import com.drunkenboys.calendarun.ui.base.BaseViewHolder
9 | import com.drunkenboys.calendarun.ui.searchschedule.model.DateItem
10 | import com.drunkenboys.calendarun.ui.searchschedule.model.DateScheduleItem
11 | import com.drunkenboys.calendarun.ui.searchschedule.model.ScheduleItem
12 |
13 | class SearchScheduleAdapter : ListAdapter>(DateScheduleItem.diffUtil) {
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder =
16 | BaseViewHolder(parent, viewType)
17 |
18 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
19 | holder.binding.setVariable(BR.item, currentList[position])
20 | holder.binding.executePendingBindings()
21 | }
22 |
23 | override fun getItemViewType(position: Int) = when (currentList[position]) {
24 | is DateItem -> R.layout.item_date
25 | is ScheduleItem -> R.layout.item_schedule
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/searchschedule/SearchScheduleDivider.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.searchschedule
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Rect
6 | import androidx.recyclerview.widget.DividerItemDecoration
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.drunkenboys.calendarun.util.extensions.dp2px
9 | import kotlin.math.roundToInt
10 |
11 | class SearchScheduleDivider(private val context: Context) : DividerItemDecoration(context, RecyclerView.VERTICAL) {
12 |
13 | private var leftInset = 16f
14 | private var rightInset = 16f
15 |
16 | private val mBounds = Rect()
17 |
18 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
19 | if (parent.layoutManager == null || drawable == null) {
20 | return
21 | }
22 | drawVertical(c, parent)
23 | }
24 |
25 | private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
26 | val drawable = drawable ?: return
27 |
28 | canvas.save()
29 | var left: Int
30 | var right: Int
31 | if (parent.clipToPadding) {
32 | left = parent.paddingLeft
33 | right = parent.width - parent.paddingRight
34 | canvas.clipRect(
35 | left, parent.paddingTop, right,
36 | parent.height - parent.paddingBottom
37 | )
38 | } else {
39 | left = 0
40 | right = parent.width
41 | }
42 | left += context.dp2px(leftInset).toInt()
43 | right -= context.dp2px(rightInset).toInt()
44 |
45 | val childCount = parent.childCount
46 | for (i in 0 until childCount) {
47 | val child = parent.getChildAt(i)
48 | if (child.tag == "DateItem") {
49 | parent.getDecoratedBoundsWithMargins(child, mBounds)
50 | val bottom = mBounds.top + child.translationY.roundToInt()
51 | val top = bottom - drawable.intrinsicHeight
52 | drawable.setBounds(left, top, right, bottom)
53 | drawable.draw(canvas)
54 | }
55 | }
56 | canvas.restore()
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/searchschedule/model/DateScheduleItem.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.searchschedule.model
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
5 | import com.drunkenboys.calendarun.util.amPm
6 | import com.drunkenboys.calendarun.util.relativeDateFormat
7 | import java.time.LocalDate
8 | import java.time.LocalDateTime
9 | import java.time.format.DateTimeFormatter
10 |
11 | sealed class DateScheduleItem {
12 |
13 | companion object {
14 |
15 | val diffUtil by lazy {
16 | object : DiffUtil.ItemCallback() {
17 |
18 | override fun areItemsTheSame(oldItem: DateScheduleItem, newItem: DateScheduleItem) =
19 | oldItem.hashCode() == newItem.hashCode()
20 |
21 | override fun areContentsTheSame(oldItem: DateScheduleItem, newItem: DateScheduleItem) = oldItem == newItem
22 | }
23 | }
24 | }
25 | }
26 |
27 | data class DateItem(val date: LocalDate) : DateScheduleItem() {
28 |
29 | override fun toString(): String = date.format(relativeDateFormat(LocalDate.now(), date))
30 | }
31 |
32 | data class ScheduleItem(val schedule: Schedule, val onClick: () -> Unit) : DateScheduleItem() {
33 |
34 | override fun toString(): String {
35 | val startDateFormat = DateTimeFormatter.ofPattern("${schedule.startDate.amPm} hh:mm")
36 | val endDateFormat = relativeDateFormat(schedule.startDate, schedule.endDate)
37 |
38 | return "${schedule.startDate.format(startDateFormat)} ~ ${schedule.endDate.format(endDateFormat)}"
39 | }
40 |
41 | fun toString(baseDateTime: LocalDateTime?): String {
42 | baseDateTime ?: return toString()
43 |
44 | val startFormat = relativeDateFormat(baseDateTime, schedule.startDate)
45 | val endFormat = relativeDateFormat(baseDateTime, schedule.endDate)
46 |
47 | return "${schedule.startDate.format(startFormat)} ~ ${schedule.endDate.format(endFormat)}"
48 | }
49 |
50 | companion object {
51 |
52 | val diffUtil by lazy {
53 | object : DiffUtil.ItemCallback() {
54 | override fun areItemsTheSame(oldItem: ScheduleItem, newItem: ScheduleItem) =
55 | oldItem.schedule.id == newItem.schedule.id
56 |
57 | override fun areContentsTheSame(oldItem: ScheduleItem, newItem: ScheduleItem) = oldItem == newItem
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/setting/SettingViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.setting
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import kotlinx.coroutines.flow.MutableSharedFlow
6 | import kotlinx.coroutines.flow.SharedFlow
7 | import kotlinx.coroutines.launch
8 |
9 | class SettingViewModel : ViewModel() {
10 |
11 | private val _manageCalendarClickEvent = MutableSharedFlow()
12 | val manageCalendarClickEvent: SharedFlow = _manageCalendarClickEvent
13 |
14 | private val _themeClickEvent = MutableSharedFlow()
15 | val themeClickEvent: SharedFlow = _themeClickEvent
16 |
17 | private val _infoClickEvent = MutableSharedFlow()
18 | val infoClickEvent: SharedFlow = _infoClickEvent
19 |
20 | private val _licenseClickEvent = MutableSharedFlow()
21 | val licenseClickEvent: SharedFlow = _licenseClickEvent
22 |
23 | fun emitManageCalendarClickEvent() {
24 | viewModelScope.launch {
25 | _manageCalendarClickEvent.emit(Unit)
26 | }
27 | }
28 |
29 | fun emitThemeClickEvent() {
30 | viewModelScope.launch {
31 | _themeClickEvent.emit(Unit)
32 | }
33 | }
34 |
35 | fun emitInfoClickEvent() {
36 | viewModelScope.launch {
37 | _infoClickEvent.emit(Unit)
38 | }
39 | }
40 |
41 | fun emitLicenseClickEvent() {
42 | viewModelScope.launch {
43 | _licenseClickEvent.emit(Unit)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/theme/CalendarThemeMapper.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.theme
2 |
3 | import com.drunkenboys.calendarun.data.calendartheme.entity.CalendarTheme
4 | import com.drunkenboys.ckscalendar.data.CalendarDesignObject
5 |
6 | fun CalendarTheme.toCalendarDesignObject() = CalendarDesignObject(
7 | weekDayTextColor = weekDayTextColor,
8 | holidayTextColor = holidayTextColor,
9 | saturdayTextColor = saturdayTextColor,
10 | sundayTextColor = sundayTextColor,
11 | selectedFrameColor = selectedFrameColor,
12 | backgroundColor = backgroundColor,
13 | textSize = textSize,
14 | textAlign = textAlign,
15 | weekSimpleStringSet = languageType.weekSimpleStringSet,
16 | weekFullStringSet = languageType.weekFullStringSet,
17 | visibleScheduleCount = visibleScheduleCount
18 | )
19 |
20 | fun CalendarDesignObject.toCalendarTheme() = CalendarTheme(
21 | weekDayTextColor = weekDayTextColor,
22 | holidayTextColor = holidayTextColor,
23 | saturdayTextColor = saturdayTextColor,
24 | sundayTextColor = sundayTextColor,
25 | selectedFrameColor = selectedFrameColor,
26 | backgroundColor = backgroundColor,
27 | textSize = textSize,
28 | textAlign = textAlign,
29 | languageType = CalendarTheme.LanguageType.KOREAN,
30 | visibleScheduleCount = visibleScheduleCount
31 | )
32 |
33 | val CalendarTheme.LanguageType.weekSimpleStringSet: List
34 | get() = when (this) {
35 | CalendarTheme.LanguageType.KOREAN -> listOf("일", "월", "화", "수", "목", "금", "토")
36 | CalendarTheme.LanguageType.ENGLISH -> listOf("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
37 | }
38 | val CalendarTheme.LanguageType.weekFullStringSet: List
39 | get() = when (this) {
40 | CalendarTheme.LanguageType.KOREAN -> listOf("일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일")
41 | CalendarTheme.LanguageType.ENGLISH -> listOf("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/theme/ThemeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.theme
2 |
3 | import android.os.Bundle
4 | import android.view.Gravity
5 | import android.view.View
6 | import androidx.navigation.navGraphViewModels
7 | import androidx.navigation.ui.setupWithNavController
8 | import com.drunkenboys.calendarun.R
9 | import com.drunkenboys.calendarun.databinding.FragmentThemeBinding
10 | import com.drunkenboys.calendarun.ui.base.BaseFragment
11 | import com.drunkenboys.calendarun.util.extensions.launchAndRepeatWithViewLifecycle
12 | import dagger.hilt.android.AndroidEntryPoint
13 | import kotlinx.coroutines.flow.collect
14 | import kotlinx.coroutines.launch
15 |
16 | @AndroidEntryPoint
17 | class ThemeFragment : BaseFragment(R.layout.fragment_theme) {
18 |
19 | private val themeViewModel by navGraphViewModels(R.id.themeFragment) { defaultViewModelProviderFactory }
20 |
21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
22 | super.onViewCreated(view, savedInstanceState)
23 | binding.viewModel = themeViewModel
24 |
25 | setupToolbar()
26 |
27 | launchAndRepeatWithViewLifecycle {
28 | launch { collectTextAlign() }
29 | }
30 | }
31 |
32 | private fun setupToolbar() {
33 | binding.toolbarTheme.setupWithNavController(navController)
34 | binding.toolbarTheme.setOnMenuItemClickListener { menu ->
35 | if (menu.itemId == R.id.menu_theme_reset) {
36 | navigateToThemeResetDialog()
37 | true
38 | } else {
39 | false
40 | }
41 | }
42 | }
43 |
44 | private fun navigateToThemeResetDialog() {
45 | val action = ThemeFragmentDirections.toThemeResetDialog()
46 | navController.navigate(action)
47 | }
48 |
49 | private suspend fun collectTextAlign() {
50 | themeViewModel.textAlign.collect { align ->
51 | when (align) {
52 | Gravity.START -> binding.tvThemeTextAlignContent.setText(R.string.start)
53 | Gravity.CENTER -> binding.tvThemeTextAlignContent.setText(R.string.center)
54 | Gravity.END -> binding.tvThemeTextAlignContent.setText(R.string.end)
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/theme/ThemeResetDialog.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.theme
2 |
3 | import android.app.AlertDialog
4 | import android.os.Bundle
5 | import androidx.fragment.app.DialogFragment
6 | import androidx.navigation.fragment.findNavController
7 | import androidx.navigation.navGraphViewModels
8 | import com.drunkenboys.calendarun.R
9 | import dagger.hilt.android.AndroidEntryPoint
10 |
11 | @AndroidEntryPoint
12 | class ThemeResetDialog : DialogFragment() {
13 |
14 | private val themeViewModel: ThemeViewModel
15 | by navGraphViewModels(R.id.themeFragment) { defaultViewModelProviderFactory }
16 |
17 | override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog = AlertDialog.Builder(requireContext())
18 | .setTitle(getString(R.string.resetTheme))
19 | .setMessage(getString(R.string.resetThemeMessage))
20 | .setPositiveButton(R.string.reset) { _, _ ->
21 | themeViewModel.resetTheme()
22 | findNavController().navigateUp()
23 | }
24 | .setNegativeButton(R.string.cancel) { _, _ -> }
25 | .create()
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/ui/theme/model/ThemeColorType.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.ui.theme.model
2 |
3 | enum class ThemeColorType {
4 | WEEKDAY,
5 | HOLIDAY,
6 | SATURDAY,
7 | SUNDAY,
8 | SELECTED_DAY_STROKE,
9 | DAY_BACKGROUND
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/DateConverter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util
2 |
3 | import com.drunkenboys.calendarun.util.DateFormatLimitType.*
4 | import java.time.Instant
5 | import java.time.LocalDate
6 | import java.time.LocalDateTime
7 | import java.time.ZoneOffset
8 | import java.time.format.DateTimeFormatter
9 |
10 | val defaultZoneOffset: ZoneOffset = ZoneOffset.systemDefault().rules.getOffset(Instant.now())
11 |
12 | fun LocalDate?.localDateToString(): String = this?.format(DateTimeFormatter.ofPattern("yyyy. M. d")) ?: ""
13 |
14 | val LocalDate.milliseconds get() = seconds * 1000
15 |
16 | val LocalDate.seconds get() = toEpochDay() * 24 * 60 * 60
17 |
18 | val LocalDateTime.amPm: String get() = if (hour < 12) "오전" else "오후"
19 |
20 | fun getStartOfDate(): LocalDateTime = LocalDate.now().atTime(0, 0, 0)
21 |
22 | fun getEndOfDate(): LocalDateTime = LocalDate.now().atTime(23, 59, 59)
23 |
24 | fun relativeDateFormat(baseDateTime: LocalDateTime, targetDateTime: LocalDateTime, limit: DateFormatLimitType = NONE)
25 | : DateTimeFormatter = when {
26 | limit == YEAR || baseDateTime.year != targetDateTime.year -> DateTimeFormatter.ofPattern("yyyy년 M월 d일 ${targetDateTime.amPm} hh:mm")
27 | limit == MONTH || baseDateTime.month != targetDateTime.month -> DateTimeFormatter.ofPattern("M월 d일 ${targetDateTime.amPm} hh:mm")
28 | limit == DAY || baseDateTime.dayOfYear != targetDateTime.dayOfYear -> DateTimeFormatter.ofPattern("d일 ${targetDateTime.amPm} hh:mm")
29 | else -> DateTimeFormatter.ofPattern("${targetDateTime.amPm} hh:mm")
30 | }
31 |
32 | fun relativeDateFormat(baseDate: LocalDate, targetDate: LocalDate): DateTimeFormatter = when {
33 | baseDate.year != targetDate.year -> DateTimeFormatter.ofPattern("yyyy년 M월 d일")
34 | else -> DateTimeFormatter.ofPattern("M월 d일")
35 | }
36 |
37 | enum class DateFormatLimitType {
38 | YEAR,
39 | MONTH,
40 | DAY,
41 | NONE
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/HorizontalInsetDividerDecoration.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Rect
6 | import androidx.recyclerview.widget.DividerItemDecoration
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.drunkenboys.calendarun.util.extensions.dp2px
9 | import kotlin.math.roundToInt
10 |
11 | class HorizontalInsetDividerDecoration(
12 | private val context: Context,
13 | private val orientation: Int,
14 | private var leftInset: Float = 0f,
15 | private var rightInset: Float = 0f,
16 | private val ignoreLast: Boolean = false
17 | ) : DividerItemDecoration(context, orientation) {
18 |
19 | private val mBounds = Rect()
20 |
21 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
22 | if (parent.layoutManager == null || drawable == null) {
23 | return
24 | }
25 | if (orientation == VERTICAL) {
26 | drawVertical(c, parent)
27 | } else {
28 | super.onDraw(c, parent, state)
29 | }
30 | }
31 |
32 | private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
33 | val drawable = drawable ?: return
34 |
35 | canvas.save()
36 | var left: Int
37 | var right: Int
38 | if (parent.clipToPadding) {
39 | left = parent.paddingLeft
40 | right = parent.width - parent.paddingRight
41 | canvas.clipRect(
42 | left, parent.paddingTop, right,
43 | parent.height - parent.paddingBottom
44 | )
45 | } else {
46 | left = 0
47 | right = parent.width
48 | }
49 | left += context.dp2px(leftInset).toInt()
50 | right -= context.dp2px(rightInset).toInt()
51 |
52 | val childCount = if (ignoreLast) parent.childCount - 1 else parent.childCount
53 | for (i in 0 until childCount) {
54 | val child = parent.getChildAt(i)
55 | parent.getDecoratedBoundsWithMargins(child, mBounds)
56 | val bottom = mBounds.bottom + child.translationY.roundToInt()
57 | val top = bottom - drawable.intrinsicHeight
58 | drawable.setBounds(left, top, right, bottom)
59 | drawable.draw(canvas)
60 | }
61 | canvas.restore()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/AnimationExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import android.view.animation.Animation
4 |
5 | fun Animation.setAnimationListener(
6 | onStart: ((Animation?) -> Unit)? = null,
7 | onEnd: ((Animation?) -> Unit)? = null,
8 | onRepeat: ((Animation?) -> Unit)? = null
9 | ) {
10 | this.setAnimationListener(object : Animation.AnimationListener {
11 | override fun onAnimationStart(animation: Animation?) {
12 | onStart?.invoke(animation)
13 | }
14 |
15 | override fun onAnimationEnd(animation: Animation?) {
16 | onEnd?.invoke(animation)
17 | }
18 |
19 | override fun onAnimationRepeat(animation: Animation?) {
20 | onRepeat?.invoke(animation)
21 | }
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/ContextExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import android.content.Context
4 | import android.util.TypedValue
5 |
6 | fun Context.dp2px(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/FlowExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.collect
5 | import kotlinx.coroutines.flow.flow
6 |
7 | // from https://proandroiddev.com/from-rxjava-to-kotlin-flow-throttling-ed1778847619
8 | fun Flow.throttleFirst(periodMillis: Long): Flow {
9 | require(periodMillis > 0) { "period should be positive" }
10 | return flow {
11 | var lastTime = 0L
12 | collect { value ->
13 | val currentTime = System.currentTimeMillis()
14 | if (currentTime - lastTime >= periodMillis) {
15 | lastTime = currentTime
16 | emit(value)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/NavigationExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavDirections
5 |
6 | /**
7 | * NavController의 현재 destination에서 해당 action으로
8 | * 이동할 수 있는 destination이 있는지 확인 후 navigate.
9 | */
10 | fun NavController.navigateSafe(action: NavDirections) {
11 | currentDestination?.getAction(action.actionId) ?: return
12 | navigate(action)
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/NetworkStateUtil.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.net.NetworkCapabilities
6 | import android.os.Build
7 |
8 | fun Context.getNetworkConnectState(): Boolean {
9 | var isConnected = false
10 | val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
11 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
12 | val capabilities = cm.getNetworkCapabilities(cm.activeNetwork)
13 | if (capabilities != null) {
14 | isConnected = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
15 | || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
16 | }
17 | } else {
18 | val activeNetwork = cm.activeNetworkInfo
19 | isConnected = activeNetwork?.isConnectedOrConnecting == true
20 | }
21 | return isConnected
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/PendingIntentExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import android.app.PendingIntent
4 | import android.os.Build
5 |
6 | object PendingIntentExt {
7 |
8 | val FLAG_UPDATE_CURRENT = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
9 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
10 | } else {
11 | PendingIntent.FLAG_UPDATE_CURRENT
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/ViewExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import android.graphics.drawable.Drawable
4 | import android.view.View
5 | import android.view.animation.Animation
6 | import android.view.animation.AnimationUtils
7 | import android.widget.TextView
8 |
9 | fun View.startAnimation(animationResId: Int) {
10 | val anim = AnimationUtils.loadAnimation(context, animationResId)
11 | this.startAnimation(anim)
12 | }
13 |
14 | fun View.startAnimation(animationResId: Int, onAnimationEnd: () -> Unit) {
15 | val anim = AnimationUtils.loadAnimation(context, animationResId)
16 | anim.setAnimationListener(object : Animation.AnimationListener {
17 | override fun onAnimationStart(animation: Animation?) {
18 | }
19 |
20 | override fun onAnimationEnd(animation: Animation?) {
21 | onAnimationEnd.invoke()
22 | }
23 |
24 | override fun onAnimationRepeat(animation: Animation?) {
25 | }
26 | })
27 | this.startAnimation(anim)
28 | }
29 |
30 | fun TextView.updateCompoundDrawablesRelativeWithIntrinsicBounds(
31 | start: Drawable? = compoundDrawablesRelative[0],
32 | top: Drawable? = compoundDrawablesRelative[1],
33 | end: Drawable? = compoundDrawablesRelative[2],
34 | bottom: Drawable? = compoundDrawablesRelative[3]
35 | ) {
36 | setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom)
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/util/extensions/ViewHolderExt.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.util.extensions
2 |
3 | import android.content.Context
4 | import androidx.recyclerview.widget.RecyclerView
5 |
6 | fun RecyclerView.ViewHolder.context(): Context {
7 | return this.itemView.context
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/view/ColorIndicatorTextView.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.view
2 |
3 | import android.content.Context
4 | import android.content.res.ColorStateList
5 | import android.content.res.TypedArray
6 | import android.util.AttributeSet
7 | import androidx.appcompat.widget.AppCompatTextView
8 | import androidx.core.content.ContextCompat
9 | import com.drunkenboys.calendarun.R
10 | import com.drunkenboys.calendarun.util.extensions.updateCompoundDrawablesRelativeWithIntrinsicBounds
11 |
12 | class ColorIndicatorTextView @JvmOverloads constructor(
13 | context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
14 | ) : AppCompatTextView(context, attrs, defStyle) {
15 |
16 | init {
17 | getAttrs(attrs, defStyle)
18 | }
19 |
20 | private fun getAttrs(attrs: AttributeSet?, defStyle: Int) {
21 | attrs ?: return
22 |
23 | val typedArray = context.obtainStyledAttributes(
24 | attrs, R.styleable.ColorIndicatorTextView, defStyle, 0
25 | )
26 |
27 | setTypedArray(typedArray)
28 | }
29 |
30 | private fun setTypedArray(typedArray: TypedArray) {
31 | val drawableEndTint = typedArray.getColor(R.styleable.ColorIndicatorTextView_indicatorTint, 0)
32 |
33 | val drawableEnd = compoundDrawablesRelative[2]
34 |
35 | drawableEnd.setTintList(ColorStateList.valueOf(drawableEndTint))
36 |
37 | typedArray.recycle()
38 | }
39 |
40 | fun setIndicatorTint(color: Int) {
41 | when (color) {
42 | ContextCompat.getColor(context, R.color.white) -> {
43 | val bg = ContextCompat.getDrawable(context, R.drawable.bg_tag_color_white_stroke_grey)
44 | updateCompoundDrawablesRelativeWithIntrinsicBounds(end = bg)
45 | }
46 | ContextCompat.getColor(context, R.color.black) -> {
47 | val bg = ContextCompat.getDrawable(context, R.drawable.bg_tag_color_black_stroke_grey)
48 | updateCompoundDrawablesRelativeWithIntrinsicBounds(end = bg)
49 | }
50 | else -> {
51 | updateCompoundDrawablesRelativeWithIntrinsicBounds(end = ContextCompat.getDrawable(context, R.drawable.bg_tag_color))
52 | compoundDrawablesRelative[2].setTintList(ColorStateList.valueOf(color))
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/view/ErrorGuideEditText.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.view
2 |
3 | import android.animation.AnimatorSet
4 | import android.animation.ObjectAnimator
5 | import android.content.Context
6 | import android.util.AttributeSet
7 | import android.view.View
8 | import androidx.appcompat.widget.AppCompatEditText
9 | import androidx.core.content.ContextCompat
10 | import androidx.core.widget.doOnTextChanged
11 | import com.drunkenboys.calendarun.R
12 | import com.drunkenboys.calendarun.util.extensions.dp2px
13 |
14 | class ErrorGuideEditText @JvmOverloads constructor(
15 | context: Context,
16 | attrs: AttributeSet? = null,
17 | defStyle: Int = R.attr.editTextStyle
18 | ) : AppCompatEditText(context, attrs, defStyle) {
19 |
20 | private val defaultHintTextColor = currentHintTextColor
21 | private val errorColor = ContextCompat.getColor(context, R.color.onError)
22 |
23 | var isError = false
24 | set(value) {
25 | if (value) startErrorAnimation()
26 | field = value
27 | }
28 |
29 | init {
30 | doOnTextChanged { _, _, _, _ ->
31 | if (isError) {
32 | setHintTextColor(defaultHintTextColor)
33 | isError = false
34 | }
35 | }
36 | }
37 |
38 | private fun startErrorAnimation() {
39 | val leftTranslationX = context.dp2px(-2f)
40 | val rightTranslationX = context.dp2px(2f)
41 |
42 | val hintTextColorAnimator = ObjectAnimator.ofArgb(
43 | this,
44 | HINT_TEXT_COLOR,
45 | currentHintTextColor,
46 | errorColor
47 | )
48 | val translationAnimator = ObjectAnimator.ofFloat(
49 | this,
50 | View.TRANSLATION_X,
51 | rightTranslationX,
52 | leftTranslationX,
53 | rightTranslationX,
54 | leftTranslationX,
55 | 0f
56 | )
57 |
58 | AnimatorSet().apply {
59 | duration = DURATION
60 | play(hintTextColorAnimator).with(translationAnimator)
61 | }.start()
62 | }
63 |
64 | companion object {
65 |
66 | private const val DURATION = 300L
67 | private const val HINT_TEXT_COLOR = "hintTextColor"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/drunkenboys/calendarun/view/ErrorGuideTextView.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.view
2 |
3 | import android.animation.AnimatorSet
4 | import android.animation.ObjectAnimator
5 | import android.content.Context
6 | import android.graphics.Rect
7 | import android.util.AttributeSet
8 | import android.view.View
9 | import androidx.appcompat.widget.AppCompatTextView
10 | import androidx.core.content.ContextCompat
11 | import androidx.core.widget.doOnTextChanged
12 | import com.drunkenboys.calendarun.R
13 | import com.drunkenboys.calendarun.util.extensions.dp2px
14 |
15 | class ErrorGuideTextView @JvmOverloads constructor(
16 | context: Context,
17 | attrs: AttributeSet? = null,
18 | defStyle: Int = R.attr.autoCompleteTextViewStyle
19 | ) : AppCompatTextView(context, attrs, defStyle) {
20 |
21 | private val defaultHintTextColor = currentHintTextColor
22 | private val errorColor = ContextCompat.getColor(context, R.color.onError)
23 |
24 | var isError = false
25 | set(value) {
26 | if (value) startErrorAnimation()
27 | field = value
28 | }
29 |
30 | init {
31 | doOnTextChanged { _, _, _, _ ->
32 | if (isError) {
33 | setHintTextColor(defaultHintTextColor)
34 | isError = false
35 | }
36 | }
37 | }
38 |
39 | override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
40 | super.onFocusChanged(focused, direction, previouslyFocusedRect)
41 | if (focused) performClick()
42 | }
43 |
44 |
45 | private fun startErrorAnimation() {
46 | val leftTranslationX = context.dp2px(-2f)
47 | val rightTranslationX = context.dp2px(2f)
48 |
49 | val hintTextColorAnimator = ObjectAnimator.ofArgb(
50 | this,
51 | HINT_TEXT_COLOR,
52 | currentHintTextColor,
53 | errorColor
54 | )
55 | val translationAnimator = ObjectAnimator.ofFloat(
56 | this,
57 | View.TRANSLATION_X,
58 | rightTranslationX,
59 | leftTranslationX,
60 | rightTranslationX,
61 | leftTranslationX,
62 | 0f
63 | )
64 |
65 | AnimatorSet().apply {
66 | duration = DURATION
67 | play(hintTextColorAnimator).with(translationAnimator)
68 | }.start()
69 | }
70 |
71 | companion object {
72 |
73 | private const val DURATION = 300L
74 | private const val HINT_TEXT_COLOR = "hintTextColor"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/hide_scale_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/show_scale_up.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/color/navigation_item_background_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/navigation_item_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/app_widget_background.xml:
--------------------------------------------------------------------------------
1 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml:
--------------------------------------------------------------------------------
1 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_check_box_normal_18.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_on_surface_radius_8dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_tag_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_tag_color_black_stroke_grey.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_tag_color_white_stroke_grey.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_white_radius_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/btn_rect_black_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_circle_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_assignment.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_check_box_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_check_box_outline_blank_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_calendar_today.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_calendar_today_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_box_pressed_18.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_favorite_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_license.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_list.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_notifications.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_palette.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_refresh.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_text_fields.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_check_box_18.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/font/inter_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/calendarun_app_widget.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
14 |
23 |
24 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
16 |
17 |
28 |
29 |
39 |
40 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_app_widget_schedule.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_date.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_drop_down_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_schedule.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
17 |
23 |
24 |
33 |
34 |
47 |
48 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main_calendar_nav_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main_calendar_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_manage_calendar_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_save_calendar_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_save_schedule_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_theme_toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_main_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-hdpi/ic_main.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_main_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-hdpi/ic_main_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_main_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-hdpi/ic_main_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-mdpi/ic_main.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_main_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-mdpi/ic_main_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_main_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-mdpi/ic_main_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xhdpi/ic_main.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_main_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xhdpi/ic_main_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_main_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xhdpi/ic_main_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxhdpi/ic_main.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_main_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxhdpi/ic_main_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_main_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxhdpi/ic_main_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxxhdpi/ic_main.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_main_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxxhdpi/ic_main_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_main_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/app/src/main/res/mipmap-xxxhdpi/ic_main_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night-v31/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #121212
4 | #ffffff
5 | #4D4D4D
6 | #F1F1F1
7 |
8 | @color/light_grey
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v31/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @string/saveSchedule_notificationNone
5 | - @string/saveSchedule_notificationTenMinutesAgo
6 | - @string/saveSchedule_notificationAHourAgo
7 | - @string/saveSchedule_notificationADayAgo
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #80FFFFFF
6 | #FFFFFFFF
7 | #FF000000
8 |
9 | #cccccc
10 | @color/white_alpha_50
11 |
12 | #4D4D4D
13 | #F1F1F1
14 | #FBC4FF
15 | #FB60C4
16 |
17 | @color/black
18 |
19 | #e20000
20 | #FFE1F5FE
21 | #FF81D4FA
22 | #FF039BE5
23 | #FF01579B
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | 0dp
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml-v31/calenda_run_app_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/calenda_run_app_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/drunkenboys/calendarun/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/drunkenboys/calendarun/data/calendar/local/FakeCalendarLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.calendar.local
2 |
3 | import com.drunkenboys.calendarun.data.calendar.entity.Calendar
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flow
6 |
7 | class FakeCalendarLocalDataSource : CalendarLocalDataSource {
8 |
9 | private val database = mutableListOf()
10 |
11 | override suspend fun insertCalendar(calendar: Calendar): Long {
12 | database.add(calendar)
13 | return database.size.toLong()
14 | }
15 |
16 | override fun fetchAllCalendar(): Flow> = flow { emit(database.toList()) }
17 |
18 | override suspend fun fetchCalendar(id: Long): Calendar = database.find { it.id == id } ?: throw IllegalArgumentException()
19 |
20 | override fun fetchCustomCalendar(): Flow> = flow { emit(database.filter { it.id != 1 }) }
21 |
22 | override suspend fun deleteCalendar(calendar: Calendar) {
23 | database.remove(calendar)
24 | }
25 |
26 | override suspend fun updateCalendar(calendar: Calendar) {
27 | val targetIndex = database.indexOfFirst { it.id == calendar.id }
28 | database.removeAt(targetIndex)
29 | database.add(targetIndex, calendar)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/test/java/com/drunkenboys/calendarun/data/schedule/local/FakeScheduleLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.schedule.local
2 |
3 | import com.drunkenboys.calendarun.data.schedule.entity.Schedule
4 | import com.drunkenboys.calendarun.util.defaultZoneOffset
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flow
7 | import java.time.LocalDateTime
8 |
9 | class FakeScheduleLocalDataSource : ScheduleLocalDataSource {
10 |
11 | private val database = mutableListOf()
12 |
13 | override suspend fun insertSchedule(schedule: Schedule): Long {
14 | database.add(schedule)
15 | return database.size.toLong()
16 | }
17 |
18 | override suspend fun fetchAllSchedule(): List {
19 | return database
20 | }
21 |
22 | override suspend fun fetchSchedule(id: Long): Schedule {
23 | return database.find { it.id == id } ?: throw IllegalArgumentException()
24 | }
25 |
26 | override fun fetchCalendarSchedules(calendarId: Long): Flow> {
27 | return flow { emit(database.filter { it.calendarId == calendarId }) }
28 | }
29 |
30 | override suspend fun updateSchedule(schedule: Schedule) {
31 | val targetIndex = database.indexOfFirst { it.id == schedule.id }
32 | database.removeAt(targetIndex)
33 | database.add(targetIndex, schedule)
34 | }
35 |
36 | override suspend fun deleteSchedule(schedule: Schedule) {
37 | database.remove(schedule)
38 | }
39 |
40 | override suspend fun fetchMatchedScheduleAfter(word: String, time: Long) =
41 | database.filter { it.startDate.toEpochSecond(defaultZoneOffset) > time && word in it.name }
42 | .sortedBy { it.startDate }
43 | .take(ScheduleDao.SCHEDULE_PAGING_SIZE)
44 |
45 | override suspend fun fetchMatchedScheduleBefore(word: String, time: Long) =
46 | database.filter { it.startDate.toEpochSecond(defaultZoneOffset) < time && word in it.name }
47 | .sortedBy { it.startDate }
48 | .takeLast(ScheduleDao.SCHEDULE_PAGING_SIZE)
49 |
50 | override fun fetchDateSchedule(date: LocalDateTime): Flow> {
51 | TODO("Not yet implemented")
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/test/java/com/drunkenboys/calendarun/data/slice/local/FakeSliceLocalDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.calendarun.data.slice.local
2 |
3 | import com.drunkenboys.calendarun.data.slice.entity.Slice
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flow
6 |
7 | class FakeSliceLocalDataSource : SliceLocalDataSource {
8 |
9 | private val database = mutableListOf()
10 |
11 | override suspend fun insertSlice(slice: Slice): Long {
12 | database.add(slice)
13 | return database.size.toLong()
14 | }
15 |
16 | override suspend fun fetchAllSlice(): List = database
17 |
18 | override suspend fun fetchSlice(id: Long) = database.find { it.id == id } ?: throw IllegalArgumentException()
19 |
20 | override fun fetchCalendarSliceList(calendarId: Long): Flow> =
21 | flow { emit(database.filter { it.calendarId == calendarId }) }
22 |
23 | override suspend fun updateSlice(slice: Slice) {
24 | val targetIndex = database.indexOfFirst { it.id == slice.id }
25 | database.removeAt(targetIndex)
26 | database.add(targetIndex, slice)
27 | }
28 |
29 | override suspend fun deleteSliceList(calendarId: Long) {
30 | database.removeAll { it.calendarId == calendarId }
31 | }
32 |
33 | override suspend fun deleteSlice(slice: Slice) {
34 |
35 | }
36 |
37 | override suspend fun deleteSliceById(id: Long) {
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | gradle_version = "7.0.3"
5 | kotlin_version = "1.5.10"
6 |
7 | google_services_version = "4.3.10"
8 | crashlytics_version = "2.8.0"
9 |
10 | compose_version = "1.0.0-rc01"
11 |
12 | mockito_version = "4.0.0"
13 | lifecycle_version = "2.4.0"
14 | nav_version = "2.3.5"
15 | room_version = "2.3.0"
16 | hilt_version = "2.40"
17 | retrofit_version = "2.9.0"
18 | coroutine_version = "1.5.2"
19 | lottieVersion = "4.2.1"
20 | }
21 |
22 | repositories {
23 | google()
24 | mavenCentral()
25 | }
26 |
27 | dependencies {
28 | classpath "com.android.tools.build:gradle:$gradle_version"
29 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
30 | // NOTE: Do not place your application dependencies here; they belong
31 | // in the individual module build.gradle files
32 | classpath "com.google.gms:google-services:$google_services_version"
33 | classpath "com.google.firebase:firebase-crashlytics-gradle:$crashlytics_version"
34 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
35 | classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
36 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
37 | classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
38 | }
39 | }
40 |
41 | task clean(type: Delete) {
42 | delete rootProject.buildDir
43 | }
44 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 26 18:17:28 KST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
3 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/library/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/library/consumer-rules.pro
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/library/src/androidTest/java/com/drunkenboys/ckscalendar/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("com.drunkenboys.ckscalendar.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/ic_main-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/boostcampwm-2021/android01-CalendaRun/368860083a7ac69c2637dc0a4e7df88c9c399aa2/library/src/main/ic_main-playstore.png
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/data/CalendarDate.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.data
2 |
3 | import java.time.LocalDate
4 |
5 | data class CalendarDate(
6 | val date: LocalDate,
7 | val dayType: DayType,
8 | var isSelected: Boolean = false
9 | )
10 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/data/CalendarDesignObject.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.data
2 |
3 | import android.graphics.Color
4 | import android.view.Gravity
5 | import androidx.annotation.ColorInt
6 | import androidx.annotation.DrawableRes
7 | import com.drunkenboys.ckscalendar.R
8 |
9 | /**
10 | * CalendarView의 디자인을 위한 설정
11 | */
12 | data class CalendarDesignObject(
13 | @ColorInt var weekDayTextColor: Int = Color.BLACK,
14 | @ColorInt var holidayTextColor: Int = ScheduleColorType.RED.color,
15 | @ColorInt var saturdayTextColor: Int = ScheduleColorType.BLUE.color,
16 | @ColorInt var sundayTextColor: Int = ScheduleColorType.RED.color,
17 | @ColorInt var selectedFrameColor: Int = ScheduleColorType.GRAY.color,
18 | @ColorInt var backgroundColor: Int = Color.WHITE,
19 | @DrawableRes var selectedFrameDrawable: Int = R.drawable.bg_month_date_selected,
20 | var textSize: Float = 10f,
21 | var textAlign: Int = Gravity.CENTER,
22 | val weekSimpleStringSet: List = listOf("일", "월", "화", "수", "목", "금", "토"),
23 | val weekFullStringSet: List = listOf("일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"),
24 | var visibleScheduleCount: Int = 3
25 | ) {
26 | companion object {
27 |
28 | fun getDefaultDesign() = CalendarDesignObject()
29 |
30 | fun getDarkDesign() = getDefaultDesign().apply {
31 | backgroundColor = Color.BLACK
32 | weekDayTextColor = Color.WHITE
33 | selectedFrameColor = Color.WHITE
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/data/CalendarScheduleObject.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.data
2 |
3 | import android.graphics.Color
4 | import com.drunkenboys.ckscalendar.utils.TimeUtils
5 | import java.time.LocalDateTime
6 |
7 | // CalendarView에 스케쥴 등을 표시하기 위한 오브젝트
8 | // TODO:
9 | data class CalendarScheduleObject(
10 | val id: Int,
11 | val color: Int = TimeUtils.getColorInt(255,245,246),
12 | val text: String,
13 | val startDate: LocalDateTime,
14 | val endDate: LocalDateTime,
15 | val isHoliday: Boolean = false
16 | )
17 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/data/CalendarSet.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.data
2 |
3 | import android.content.Context
4 | import com.drunkenboys.ckscalendar.R
5 | import com.drunkenboys.ckscalendar.utils.toLong
6 | import java.time.LocalDate
7 |
8 | data class CalendarSet(
9 | val id: Int,
10 | val name: String,
11 | val startDate: LocalDate,
12 | val endDate: LocalDate,
13 | ) {
14 | companion object {
15 |
16 | fun generateCalendarOfYear(context: Context, year: Int): List {
17 | return (1..12).map { month ->
18 | val start = LocalDate.of(year, month, 1)
19 | val end = LocalDate.of(year, month, start.lengthOfMonth())
20 | CalendarSet(
21 | id = start.toLong().toInt(),
22 | name = "${year}${context.getString(R.string.common_year)} ${month}${context.getString(R.string.common_month)}",
23 | startDate = start,
24 | endDate = end
25 | )
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/data/DayType.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.data
2 |
3 | enum class DayType {
4 | WEEKDAY,
5 | HOLIDAY,
6 | SATURDAY,
7 | SUNDAY,
8 | PADDING
9 | }
10 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/data/ScheduleColorType.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.data
2 |
3 | import com.drunkenboys.ckscalendar.utils.TimeUtils
4 |
5 | enum class ScheduleColorType(val color: Int) {
6 | RED(TimeUtils.getColorInt(220, 34, 39)),
7 | ORANGE(TimeUtils.getColorInt(255, 113, 67)),
8 | YELLOW(TimeUtils.getColorInt(254, 193, 7)),
9 | GREEN(TimeUtils.getColorInt(139, 194, 74)),
10 | BLUE(TimeUtils.getColorInt(75, 181, 222)),
11 | NAVY(TimeUtils.getColorInt(3, 169, 245)),
12 | PURPLE(TimeUtils.getColorInt(156, 40, 177)),
13 | GRAY(TimeUtils.getColorInt(225, 225, 225)),
14 | CYAN(TimeUtils.getColorInt(71, 214, 220)),
15 | MAGENTA(TimeUtils.getColorInt(255, 184, 120))
16 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/listener/OnDayClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.listener
2 |
3 | import java.time.LocalDate
4 |
5 | fun interface OnDayClickListener {
6 |
7 | fun onDayClick(date: LocalDate, position: Int)
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/listener/OnDaySecondClickListener.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.listener
2 |
3 | import java.time.LocalDate
4 |
5 | fun interface OnDaySecondClickListener {
6 |
7 | fun onDayClick(date: LocalDate, position: Int)
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/monthcalendar/MonthCellPositionStore.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.monthcalendar
2 |
3 | data class MonthCellPositionStore(
4 | val savedLineIndex: Int,
5 | val lastSelectedPosition: Int
6 | )
7 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/monthcalendar/MonthState.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.monthcalendar
2 |
3 | import android.os.Parcelable
4 | import android.view.View
5 | import kotlinx.parcelize.Parcelize
6 |
7 | @Parcelize
8 | class MonthState(
9 | val parcelable: Parcelable,
10 | val lastPageName: String = "",
11 | val lastPagePosition: Int = -1,
12 | val lastCalendarType: CalendarType = CalendarType.NONE
13 | ) : View.BaseSavedState(parcelable) {
14 |
15 | enum class CalendarType {
16 | DEFAULT, CUSTOM, NONE
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/monthcalendar/OnDaySelectStateListener.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.monthcalendar
2 |
3 | fun interface OnDaySelectStateListener {
4 |
5 | fun onDaySelectChange(pagePosition: Int, dayPosition: Int)
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/utils/CalendarSetConverter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import com.drunkenboys.ckscalendar.data.CalendarDate
4 | import com.drunkenboys.ckscalendar.data.CalendarScheduleObject
5 | import com.drunkenboys.ckscalendar.data.CalendarSet
6 | import com.drunkenboys.ckscalendar.data.DayType
7 | import java.time.DayOfWeek
8 |
9 | fun calendarSetToCalendarDatesList(calendarSet: CalendarSet, schedules: List): List> {
10 | val holidaySchedule = schedules.filter { schedule -> schedule.isHoliday }
11 | .map { schedule -> schedule.startDate.toLocalDate() }
12 | .sortedDescending()
13 |
14 | // n주
15 | val weekList = mutableListOf>()
16 | var oneDay = calendarSet.startDate
17 | var paddingPrev = calendarSet.startDate
18 | var paddingNext = calendarSet.endDate
19 |
20 | // 앞쪽 패딩
21 | if (paddingPrev.dayOfWeek != DayOfWeek.SUNDAY) weekList.add(mutableListOf())
22 | while (paddingPrev.dayOfWeek != DayOfWeek.SUNDAY) {
23 | paddingPrev = paddingPrev.minusDays(1)
24 | weekList.last().add(CalendarDate(paddingPrev, DayType.PADDING))
25 | }
26 |
27 | // n일 추가
28 | while (oneDay <= calendarSet.endDate) {
29 |
30 | // 일요일에는 1주일 갱신
31 | if (oneDay.dayOfWeek == DayOfWeek.SUNDAY) weekList.add(mutableListOf())
32 |
33 | // 1일 추가
34 | weekList.last()
35 | .add(
36 | CalendarDate(
37 | date = oneDay,
38 | dayType = if (oneDay in holidaySchedule) DayType.HOLIDAY
39 | else TimeUtils.parseDayWeekToDayType(oneDay.dayOfWeek)
40 | )
41 | )
42 |
43 | oneDay = oneDay.plusDays(1L)
44 | }
45 |
46 | // 뒤쪽 패딩
47 | while (paddingNext.dayOfWeek != DayOfWeek.SATURDAY) {
48 | paddingNext = paddingNext.plusDays(1)
49 | weekList.last().add(CalendarDate(paddingNext, DayType.PADDING))
50 | }
51 |
52 | return weekList
53 | }
54 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/utils/ComposeStateExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.runtime.*
5 | import kotlinx.coroutines.flow.collect
6 | import java.time.DayOfWeek
7 | import java.time.LocalDate
8 | import java.util.*
9 |
10 |
11 |
12 | // 참고: https://ichi.pro/ko/jetpack-compose-ui-muhan-moglog-peiji-moglog-194681336448872
13 | @Composable
14 | fun LazyListState.ShouldNextScroll(
15 | onLoadMore : () -> Unit
16 | ) {
17 |
18 | // emit
19 | val shouldLoadMore = remember {
20 | derivedStateOf {
21 | with(layoutInfo) {
22 | // 화면에 보이는 마지막 인덱스
23 | val lastIndex = visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true
24 |
25 | lastIndex.index >= totalItemsCount - 1
26 | }
27 | }
28 | }
29 |
30 | // job
31 | LaunchedEffect(shouldLoadMore) {
32 | // collect
33 | snapshotFlow { shouldLoadMore.value }.collect { should ->
34 | if (should) { onLoadMore() }
35 | }
36 | }
37 | }
38 |
39 | @Composable
40 | fun LazyListState.ShouldPrevScroll(
41 | onLoadMore: () -> Unit
42 | ) {
43 |
44 | val shouldLoadMore = remember {
45 | derivedStateOf {
46 | with(layoutInfo) {
47 | //화면에 보이는 첫번째 인덱스
48 | val firstIndex = visibleItemsInfo.firstOrNull() ?: return@derivedStateOf true
49 |
50 | firstIndex.index <= 1
51 | }
52 | }
53 | }
54 |
55 | // job
56 | LaunchedEffect(shouldLoadMore) {
57 | // collect
58 | snapshotFlow { shouldLoadMore.value }.collect { should ->
59 | if (should) { onLoadMore() }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/utils/DateConverter.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import java.time.Instant
4 | import java.time.LocalDate
5 | import java.time.ZoneOffset
6 | import java.time.format.DateTimeFormatter
7 |
8 | val defaultZoneOffset: ZoneOffset = ZoneOffset.systemDefault().rules.getOffset(Instant.now())
9 |
10 | fun stringToLocalDate(date: String): LocalDate =
11 | LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy.M.d"))
12 |
13 | fun LocalDate?.localDateToString(): String = this?.format(DateTimeFormatter.ofPattern("yyyy.M.d")) ?: ""
14 |
15 | fun LocalDate.toLong() = toEpochDay() * 24 * 60 * 60 * 1000
16 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/utils/GravityMapper.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import android.text.Layout
4 | import android.view.Gravity
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.text.style.TextAlign
7 |
8 | object GravityMapper {
9 | fun toTextAlign(gravity: Int): TextAlign = when (gravity) {
10 | Gravity.CENTER -> TextAlign.Center
11 | Gravity.END -> TextAlign.End
12 | Gravity.RIGHT -> TextAlign.Right
13 | Gravity.LEFT -> TextAlign.Left
14 | Gravity.START -> TextAlign.Start
15 | else -> TextAlign.Center
16 | }
17 |
18 | fun toColumnAlign(gravity: Int): Alignment.Horizontal = when (gravity) {
19 | Gravity.CENTER -> Alignment.CenterHorizontally
20 | Gravity.END -> Alignment.End
21 | Gravity.START -> Alignment.Start
22 | else -> Alignment.CenterHorizontally
23 | }
24 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/utils/TimeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import com.drunkenboys.ckscalendar.data.DayType
4 | import java.time.DayOfWeek
5 | import java.time.LocalDate
6 | import java.time.Period
7 |
8 | object TimeUtils {
9 | fun getColorInt(R: Int, G: Int, B: Int): Int {
10 | return 0xff shl 24 or (R and 0xff shl 16) or (G and 0xff shl 8) or (B and 0xff)
11 | }
12 |
13 | fun parseDayWeekToDayType(week: DayOfWeek): DayType {
14 | return when (week) {
15 | DayOfWeek.SATURDAY -> DayType.SATURDAY
16 | DayOfWeek.SUNDAY -> DayType.SUNDAY
17 | else -> DayType.WEEKDAY
18 | }
19 | }
20 |
21 | // 일요일 시작 기준 요일값
22 | fun DayOfWeek.dayValue() = this.value % 7
23 |
24 | fun LocalDate.isSameWeek(endDate: LocalDate): Boolean {
25 | val weekDuration = (endDate.dayOfWeek.dayValue()) - (this.dayOfWeek.dayValue())
26 | val dayDuration = Period.between(this, endDate).days
27 | return weekDuration == dayDuration
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/utils/ViewExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import android.content.Context
4 | import android.graphics.drawable.DrawableContainer
5 | import android.graphics.drawable.GradientDrawable
6 | import android.graphics.drawable.StateListDrawable
7 | import android.util.TypedValue
8 | import android.view.View
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.platform.LocalDensity
11 | import androidx.compose.ui.unit.Dp
12 | import androidx.recyclerview.widget.RecyclerView
13 |
14 | fun Context.dp2px(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
15 |
16 | fun View.tintStroke(color: Int, width: Float) {
17 | this.resources.displayMetrics.density.let { scale ->
18 | this.background?.constantState?.let {
19 | val origin = (it.newDrawable().mutate())
20 | when (origin) {
21 | is GradientDrawable -> origin.setStroke((width * scale).toInt(), color)
22 | is StateListDrawable -> {
23 | val drawableContainerState = origin.constantState as DrawableContainer.DrawableContainerState
24 | val children = drawableContainerState.children
25 | children.filterNotNull()
26 | .forEach {
27 | it as GradientDrawable
28 | it.setStroke((width * scale).toInt(), color)
29 | }
30 | }
31 | }
32 | this.background = origin
33 | }
34 | }
35 | }
36 |
37 | fun RecyclerView.ViewHolder.context(): Context {
38 | return this.itemView.context
39 | }
40 |
41 | @Composable
42 | fun Int.dp() = with(LocalDensity.current) { Dp(this@dp.toFloat()).toSp() }
43 |
44 | @Composable
45 | fun Float.dp() = with(LocalDensity.current) { Dp(this@dp).toSp() }
46 |
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/CustomTheme.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.yearcalendar
2 |
3 | import androidx.compose.material.MaterialTheme
4 | import androidx.compose.material.lightColors
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.graphics.Color
7 | import com.drunkenboys.ckscalendar.data.CalendarDesignObject
8 |
9 | /**
10 | * FIXME: View에서 Theme과 design의 color를 혼용하고 있음.
11 | * Theme
12 | */
13 | @Composable
14 | fun CustomTheme(
15 | design: CalendarDesignObject,
16 | content: @Composable () -> Unit
17 | ) {
18 | val lightColors = lightColors(
19 | primary = Color(design.weekDayTextColor),
20 | primaryVariant = Color(design.selectedFrameColor),
21 | secondary = Color(design.saturdayTextColor),
22 | secondaryVariant = Color(design.sundayTextColor),
23 | background = Color(design.backgroundColor),
24 | )
25 |
26 | // 존재하는 MaterialTheme에서 color set을 커스텀.
27 | // View를 선언할 때 MaterialTheme로 감싸면 테마를 적용할 수 있다.
28 | MaterialTheme(
29 | colors = lightColors,
30 | content = content
31 | )
32 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/composeView/BaseText.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.yearcalendar.composeView
2 |
3 | import androidx.compose.material.MaterialTheme
4 | import androidx.compose.material.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.text.font.FontWeight
9 | import androidx.compose.ui.text.style.TextAlign
10 | import com.drunkenboys.ckscalendar.data.CalendarDesignObject
11 | import com.drunkenboys.ckscalendar.data.DayType
12 | import com.drunkenboys.ckscalendar.utils.dp
13 |
14 | @Composable
15 | fun BaseText(
16 | text: String = " ",
17 | type: DayType,
18 | modifier: Modifier,
19 | fontWeight: FontWeight? = null,
20 | textAlign: TextAlign? = null,
21 | design: CalendarDesignObject
22 | ) {
23 | Text(
24 | text = text,
25 | modifier = modifier,
26 | fontSize = design.textSize.dp(),
27 | color = when (type) {
28 | DayType.PADDING -> Color.Transparent
29 | DayType.HOLIDAY -> Color(design.holidayTextColor)
30 | DayType.SATURDAY -> Color(design.saturdayTextColor)
31 | DayType.SUNDAY -> Color(design.sundayTextColor)
32 | else -> MaterialTheme.colors.primary
33 | },
34 | fontWeight = fontWeight,
35 | textAlign = textAlign
36 | )
37 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/composeView/DayText.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.yearcalendar.composeView
2 |
3 | import androidx.compose.foundation.border
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.shape.CircleShape
6 | import androidx.compose.material.MaterialTheme
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.text.font.FontWeight
12 | import androidx.compose.ui.unit.dp
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import com.drunkenboys.ckscalendar.data.CalendarDate
15 | import com.drunkenboys.ckscalendar.data.DayType
16 | import com.drunkenboys.ckscalendar.utils.GravityMapper
17 | import com.drunkenboys.ckscalendar.utils.dp
18 | import com.drunkenboys.ckscalendar.yearcalendar.CustomTheme
19 | import com.drunkenboys.ckscalendar.yearcalendar.YearCalendarViewModel
20 | import java.time.LocalDate
21 |
22 | /**
23 | * 하루 날짜를 표시합니다.
24 | */
25 | @Composable
26 | fun DayText(
27 | day: CalendarDate,
28 | viewModel: YearCalendarViewModel,
29 | isFirstOfCalendarSet: Boolean
30 | ) {
31 | BaseText(
32 | text = if (isFirstOfCalendarSet) { "${day.date.monthValue}. " } else { "" } + "${day.date.dayOfMonth}",
33 | type = day.dayType,
34 | modifier = Modifier.border(
35 | width = 1.dp,
36 | shape = CircleShape,
37 | color = if (LocalDate.now() == day.date) MaterialTheme.colors.primary else Color.Transparent
38 | ).padding(5.dp),
39 | textAlign = GravityMapper.toTextAlign(viewModel.design.value.textAlign),
40 | fontWeight = if (isFirstOfCalendarSet) FontWeight.Bold else null,
41 | design = viewModel.design.value
42 | )
43 | }
44 |
45 | @Preview
46 | @Composable
47 | fun PreviewDayText() {
48 | val viewModel = YearCalendarViewModel()
49 | CustomTheme(design = viewModel.design.value) {
50 | DayText(day = CalendarDate(date = LocalDate.now(), dayType = DayType.WEEKDAY), viewModel = viewModel, true)
51 | }
52 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/composeView/MonthHeader.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.yearcalendar.composeView
2 |
3 | import androidx.compose.animation.core.Spring
4 | import androidx.compose.animation.core.animateFloatAsState
5 | import androidx.compose.animation.core.spring
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.lazy.LazyListState
9 | import androidx.compose.material.Card
10 | import androidx.compose.material.MaterialTheme
11 | import androidx.compose.material.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.alpha
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.tooling.preview.Preview
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.zIndex
20 | import com.drunkenboys.ckscalendar.yearcalendar.CustomTheme
21 | import com.drunkenboys.ckscalendar.yearcalendar.YearCalendarViewModel
22 |
23 | /**
24 | * 월 표시
25 | */
26 | @Composable
27 | fun AnimatedMonthHeader(
28 | listState: LazyListState,
29 | monthName: String
30 | ) {
31 | // 0 ~ 1을 부드럽게 오가는 애니메이션
32 | // 스크롤을 하는 중이면 1이 된다.
33 | val density: Float by animateFloatAsState(
34 | targetValue = if(listState.isScrollInProgress) 1f else 0f,
35 | animationSpec = spring(
36 | dampingRatio = Spring.DampingRatioNoBouncy,
37 | stiffness = Spring.StiffnessMedium
38 | )
39 | )
40 |
41 | // 스크롤 할 때 투명도가 사라지는 애니메이션
42 | Card(
43 | modifier = Modifier.alpha(density).zIndex(10f),
44 | backgroundColor = MaterialTheme.colors.background,
45 | elevation = 10.dp
46 | ) {
47 | Text(
48 | text = monthName,
49 | color = MaterialTheme.colors.primary,
50 | modifier = Modifier.padding(start = 20.dp, end = 20.dp,top = 5.dp, bottom = 5.dp)
51 | )
52 | }
53 | }
54 |
55 | @Preview
56 | @Composable
57 | fun PreviewMonthHeader() {
58 | val viewModel = YearCalendarViewModel()
59 | CustomTheme(design = viewModel.design.value) {
60 | Card(
61 | modifier = Modifier.wrapContentSize().background(color = Color.Black),
62 | elevation = 200.dp,
63 | ) {
64 | Text(
65 | text = "1월",
66 | color = MaterialTheme.colors.primary,
67 | modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 5.dp, bottom = 5.dp)
68 | )
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/composeView/README.md:
--------------------------------------------------------------------------------
1 | # Year Calendar View Hierarchy
2 |
3 | ```
4 | |-- YearCalendarView(LinearLayout)
5 | | |-- WeekHeader(요일 표시)
6 | | |-- CalendarLazyColumn(전체 달력)
7 | | | |-- MonthHeader(월 표시)
8 | | | |-- YearHeader(년 표시)
9 | | | |-- WeekCalendar(1주일 달력)
10 | | | | |-- DayText(날짜 표시)
11 | | | | |-- ScheduleText(일정 표시)
12 | ```
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/composeView/WeekHeader.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.yearcalendar.composeView
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.material.MaterialTheme
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.text.style.TextAlign
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import com.drunkenboys.ckscalendar.utils.GravityMapper
14 | import com.drunkenboys.ckscalendar.yearcalendar.CustomTheme
15 | import com.drunkenboys.ckscalendar.yearcalendar.YearCalendarViewModel
16 |
17 | /**
18 | * 요일 표시
19 | * 일월화수목금토 를 표시한다.
20 | */
21 | @Composable
22 | fun WeekHeader(
23 | viewModel: YearCalendarViewModel
24 | ) {
25 | Row(
26 | modifier = Modifier.fillMaxWidth().background(MaterialTheme.colors.background),
27 | horizontalArrangement = Arrangement.SpaceEvenly
28 | ) {
29 |
30 | // design 속성에 있는 Simple String Set을 모두 표시한다.
31 | viewModel.design.value.weekSimpleStringSet.forEach { dayId ->
32 | Text(
33 | text = dayId,
34 | color = MaterialTheme.colors.primary,
35 | textAlign = GravityMapper.toTextAlign(viewModel.design.value.textAlign)
36 | )
37 | }
38 | }
39 | }
40 |
41 | @Preview
42 | @Composable
43 | fun PreviewWeekHeader() {
44 | val viewModel = YearCalendarViewModel()
45 |
46 | CustomTheme(design = viewModel.design.value) {
47 | WeekHeader(viewModel = viewModel)
48 | }
49 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/drunkenboys/ckscalendar/yearcalendar/composeView/YearHeader.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.yearcalendar.composeView
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.MaterialTheme
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.text.style.TextAlign
10 | import androidx.compose.ui.unit.dp
11 | import com.drunkenboys.ckscalendar.data.CalendarDate
12 | import com.drunkenboys.ckscalendar.data.DayType
13 | import java.time.LocalDate
14 |
15 | @Composable
16 | fun YearHeader(week: List) {
17 | val startDate = week.first { day -> day.dayType != DayType.PADDING }.date
18 | val endDate = week.last { day -> day.dayType != DayType.PADDING }.date
19 | val firstOfYear = LocalDate.of(endDate.year, 1, 1)
20 |
21 | // 해가 갱신될 때마다 상단에 연표시
22 | if (firstOfYear in startDate..endDate)
23 | Text(
24 | text = "${startDate.year}년",
25 | color = MaterialTheme.colors.primary,
26 | modifier = Modifier
27 | .fillMaxWidth()
28 | .padding(top = 16.dp, bottom = 16.dp),
29 | textAlign = TextAlign.Center
30 | )
31 | }
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_calendar_line_05px.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_calendar_today.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/res/drawable/bg_month_date_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/item_month_cell.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
17 |
18 |
26 |
27 |
28 |
37 |
38 |
47 |
48 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/item_month_page.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/layout_week_calendar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/layout/layout_year_calendar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/library/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 |
6 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #FFFFFFFF
6 | #FF000000
7 | #CFCFCF
8 | #80F7F7F7
9 | #9C27B0
10 |
11 |
--------------------------------------------------------------------------------
/library/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10dp
4 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 월
4 | 년
5 | 일
6 | 월
7 | 화
8 | 수
9 | 목
10 | 금
11 | 토
12 |
13 |
--------------------------------------------------------------------------------
/library/src/test/java/com/drunkenboys/ckscalendar/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/library/src/test/java/com/drunkenboys/ckscalendar/utils/CalendarSetConverterTest.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import com.drunkenboys.ckscalendar.data.CalendarSet
4 | import org.junit.Assert.*
5 |
6 | import org.junit.Test
7 | import java.time.LocalDate
8 |
9 | class CalendarSetConverterTest {
10 | private val id = 0
11 | private val name = "test"
12 |
13 | private val month1DayStart = LocalDate.of(2021, 1, 1)
14 | private val month1Day2 = LocalDate.of(2021, 1, 2)
15 | private val month1DayMid = LocalDate.of(2021, 1, 15)
16 | private val month1DayEnd = LocalDate.of(2021, 1, 31)
17 |
18 | private val month2DayStart = LocalDate.of(2021, 2, 1)
19 | private val month2DayMid = LocalDate.of(2021, 2, 15)
20 |
21 | @Test
22 | fun `6주일이_한_달인_슬라이스를_변환하면_6줄`() {
23 | // Given 1.1(금) ~ 1.31(일)
24 | val calendarSet = CalendarSet(
25 | id,
26 | name,
27 | startDate = month1DayStart,
28 | endDate = month1DayEnd
29 | )
30 |
31 | // When 변환
32 | val calendarDateList = calendarSetToCalendarDatesList(calendarSet, listOf())
33 |
34 | assertEquals(6, calendarDateList.size)
35 | }
36 |
37 | @Test
38 | fun `6주일이_두_달_걸친_슬라이스를_변환하면_6줄`() {
39 | // Given 1.15(목) ~ 2.15(월)
40 | val calendarSet = CalendarSet(
41 | id,
42 | name,
43 | startDate = month1DayMid,
44 | endDate = month2DayMid
45 | )
46 |
47 | // When convert
48 | val calendarDateList = calendarSetToCalendarDatesList(calendarSet, listOf())
49 |
50 | // Then 6줄
51 | assertEquals(6, calendarDateList.size)
52 | }
53 |
54 | @Test
55 | fun `1주일_보다_짧은_슬라이스를_변환하면_1줄`() {
56 | // Given 1.1 ~ 1.2
57 | val calendarSet = CalendarSet(
58 | id,
59 | name,
60 | startDate = month1DayStart,
61 | endDate = month1Day2
62 | )
63 |
64 | // When convert
65 | val calendarDateList = calendarSetToCalendarDatesList(calendarSet, listOf())
66 |
67 | // Then list is not empty
68 | assertEquals(1, calendarDateList.size)
69 | assertTrue(calendarDateList.isNotEmpty())
70 | assertTrue(calendarDateList[0].isNotEmpty())
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/library/src/test/java/com/drunkenboys/ckscalendar/utils/UtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.drunkenboys.ckscalendar.utils
2 |
3 | import com.drunkenboys.ckscalendar.utils.TimeUtils.isSameWeek
4 | import org.junit.Assert.*
5 |
6 | import org.junit.After
7 | import org.junit.Before
8 | import org.junit.Test
9 | import java.time.LocalDate
10 |
11 | class UtilsTest {
12 |
13 | @Before
14 | fun setUp() {
15 | }
16 |
17 | @After
18 | fun tearDown() {
19 |
20 | }
21 |
22 | @Test
23 | fun `저번주_목요일과_이번주_화요일이_다른주인지_테스트`() {
24 | // Given 저번 목요일, 이번 수요일
25 | val prev = LocalDate.of(2021, 11, 4) //목
26 | val next = LocalDate.of(2021, 11, 9) //화
27 |
28 | // Then 다른 주
29 | assertEquals(false, prev.isSameWeek(next))
30 | }
31 |
32 | @Test
33 | fun `이번주_일요일과_이번주_토요일이_같은주인지_테스트`() {
34 | // Given 이번 일요일, 이번 토요일
35 | val prev = LocalDate.of(2021, 11, 7) //일
36 | val next = LocalDate.of(2021, 11, 13) //토
37 |
38 | // Then 같은 주
39 | assertEquals(true, prev.isSameWeek(next))
40 | }
41 |
42 | @Test
43 | fun `같은날이_같은주인지_테스트`() {
44 | // Given 이전 목요일, 이후 수요일
45 | val prev = LocalDate.of(2021, 11, 7)
46 | val next = LocalDate.of(2021, 11, 7)
47 |
48 | // Then 같은 주
49 | assertEquals(true, prev.isSameWeek(next))
50 | }
51 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "달려달력"
10 | include ':app'
11 | include ':library'
12 |
--------------------------------------------------------------------------------