├── app ├── .gitignore ├── src │ ├── main │ │ ├── ic_app_icon-playstore.png │ │ ├── res │ │ │ ├── font │ │ │ │ ├── pretendard_bold.otf │ │ │ │ ├── pretendard_regular.otf │ │ │ │ ├── pretendard_semibold.otf │ │ │ │ ├── font_pretendard_bold.xml │ │ │ │ ├── font_pretendard_regular.xml │ │ │ │ └── font_pretendard_semibold.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_app_icon.webp │ │ │ │ ├── ic_app_icon_round.webp │ │ │ │ └── ic_app_icon_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_app_icon.webp │ │ │ │ ├── ic_app_icon_round.webp │ │ │ │ └── ic_app_icon_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_app_icon.webp │ │ │ │ ├── ic_app_icon_round.webp │ │ │ │ └── ic_app_icon_foreground.webp │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ └── spinner_array.xml │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_app_icon.webp │ │ │ │ ├── ic_app_icon_round.webp │ │ │ │ └── ic_app_icon_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_app_icon.webp │ │ │ │ ├── ic_app_icon_round.webp │ │ │ │ └── ic_app_icon_foreground.webp │ │ │ ├── drawable │ │ │ │ ├── shape_regular_fill_30_rect.xml │ │ │ │ ├── shape_blue50_fill_14_rect.xml │ │ │ │ ├── shape_gray50_fill_10_rect.xml │ │ │ │ ├── shape_blue300_fill_30_rect.xml │ │ │ │ ├── shape_gray200_fill_10_rect.xml │ │ │ │ ├── shape_mint400_fill_10_rect.xml │ │ │ │ ├── checkbox_selector.xml │ │ │ │ ├── shpae_mint50_fill_40_rect.xml │ │ │ │ ├── shape_gray200_fill_20_rect.xml │ │ │ │ ├── shape_gray200_fill_40_rect.xml │ │ │ │ ├── shape_gray200_fill_90_rect.xml │ │ │ │ ├── shape_mint400_fill_20_rect.xml │ │ │ │ ├── shape_mint400_fill_24_rect.xml │ │ │ │ ├── shape_mint400_fill_30_rect.xml │ │ │ │ ├── shape_mint400_fill_40_rect.xml │ │ │ │ ├── shape_mint900_fill_50_rect.xml │ │ │ │ ├── sel_date_day_text.xml │ │ │ │ ├── shape_gradient_fill_rect.xml │ │ │ │ ├── shape_regular_line_16_rect.xml │ │ │ │ ├── bg_spinner.xml │ │ │ │ ├── shape_regular_line_rect.xml │ │ │ │ ├── shape_white_fill_top25_rect.xml │ │ │ │ ├── shape_gray50_line_12_rect.xml │ │ │ │ ├── sel_home_nav_selected_item.xml │ │ │ │ ├── sel_quick_scan_tab_indicator.xml │ │ │ │ ├── shape_gray900_fill_circle.xml │ │ │ │ ├── sel_date_day_button.xml │ │ │ │ ├── sel_login_button.xml │ │ │ │ ├── sel_setting_nav_selected_item.xml │ │ │ │ ├── sel_signup_button.xml │ │ │ │ ├── sel_storage_nav_selected_item.xml │ │ │ │ ├── sel_createaccount_button.xml │ │ │ │ ├── shape_gray100_fill_8_rect.xml │ │ │ │ ├── shape_regular_line_4_rect.xml │ │ │ │ ├── shape_white_line_30_rect.xml │ │ │ │ ├── shape_white_line_50_rect.xml │ │ │ │ ├── shape_white_line_40_rect.xml │ │ │ │ ├── bg_spinner_down.xml │ │ │ │ ├── bg_spinner_up.xml │ │ │ │ ├── sel_notice_detail_like.xml │ │ │ │ ├── sel_notice_detail_bookmark.xml │ │ │ │ ├── sel_quick_scan_bookmark.xml │ │ │ │ ├── ic_box.xml │ │ │ │ ├── ic_cursor.xml │ │ │ │ ├── ic_check_box_checked.xml │ │ │ │ ├── bg_student_id_spinner.xml │ │ │ │ ├── ic_check.xml │ │ │ │ ├── bg_date_gradient.xml │ │ │ │ ├── ic_date_arrow.xml │ │ │ │ ├── ic_setting_arrow.xml │ │ │ │ ├── ic_date_close.xml │ │ │ │ ├── ic_notice_detail_bookmark_on.xml │ │ │ │ ├── ic_signup_right_arrow.xml │ │ │ │ ├── ic_notice_post_arrow.xml │ │ │ │ ├── ic_notice_post_delete.xml │ │ │ │ ├── ic_handle.xml │ │ │ │ ├── ic_notice_detail_bookmark_off.xml │ │ │ │ ├── ic_check_box_unchecked.xml │ │ │ │ ├── ic_quick_scan_bookmark_selected.xml │ │ │ │ ├── ic_bottomsheet_notice_post_target_delete.xml │ │ │ │ ├── ic_quick_scan_bookmark_unselected.xml │ │ │ │ ├── ic_nav_storage_on.xml │ │ │ │ ├── ic_nav_storage_off.xml │ │ │ │ ├── ic_back.xml │ │ │ │ ├── ic_home_floating_action_btn.xml │ │ │ │ ├── ic_nav_home_on.xml │ │ │ │ ├── ic_nav_home_off.xml │ │ │ │ ├── ic_notice_post_image_delete.xml │ │ │ │ ├── ic_home_views.xml │ │ │ │ ├── img_quick_scan_placeholder.xml │ │ │ │ ├── ic_notice_post_date.xml │ │ │ │ ├── ic_notice_detail_like.xml │ │ │ │ ├── ic_notice_post_photo.xml │ │ │ │ ├── ic_home_like.xml │ │ │ │ └── ic_notice_detail_dislike.xml │ │ │ ├── color │ │ │ │ └── sel_color_bnv_menu.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_app_icon.xml │ │ │ │ └── ic_app_icon_round.xml │ │ │ ├── values-v31 │ │ │ │ └── themes.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── values-v23 │ │ │ │ └── themes.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── layout │ │ │ │ ├── item_signup_list.xml │ │ │ │ ├── activity_splash.xml │ │ │ │ ├── item_date_number.xml │ │ │ │ ├── item_bottom_button.xml │ │ │ │ ├── item_bottom_button_create.xml │ │ │ │ ├── item_home_notice_category.xml │ │ │ │ ├── item_notice_detail_image.xml │ │ │ │ ├── item_toolbar.xml │ │ │ │ ├── item_notice_post_image.xml │ │ │ │ └── fragment_storage.xml │ │ │ └── menu │ │ │ │ └── menu_main_nav.xml │ │ └── java │ │ │ └── com │ │ │ └── univoice │ │ │ ├── domain │ │ │ ├── repository │ │ │ │ ├── LoginRepository.kt │ │ │ │ ├── SettingRepository.kt │ │ │ │ ├── StorageRepository.kt │ │ │ │ ├── QuickScanRepository.kt │ │ │ │ ├── PostRepository.kt │ │ │ │ ├── UserInfoRepository.kt │ │ │ │ ├── HomeRepository.kt │ │ │ │ ├── NoticeDetailRepository.kt │ │ │ │ └── SignUpRepository.kt │ │ │ └── entity │ │ │ │ ├── HomeQuickScanListEntity.kt │ │ │ │ ├── NoticeListEntity.kt │ │ │ │ ├── StorageListEntity.kt │ │ │ │ ├── SettingUserEntity.kt │ │ │ │ ├── QuickScanListEntity.kt │ │ │ │ └── NoticeDetailEntity.kt │ │ │ ├── app │ │ │ ├── di │ │ │ │ ├── Extension.kt │ │ │ │ ├── Qualifier.kt │ │ │ │ ├── UserPreferencesModule.kt │ │ │ │ ├── HomeModule.kt │ │ │ │ ├── PostModule.kt │ │ │ │ ├── SettingModule.kt │ │ │ │ ├── SignUpModule.kt │ │ │ │ ├── StorageModule.kt │ │ │ │ ├── QuickScanModule.kt │ │ │ │ ├── NoticeDetailModule.kt │ │ │ │ └── LoginModule.kt │ │ │ └── interceptor │ │ │ │ └── TokenInterceptor.kt │ │ │ ├── feature │ │ │ ├── util │ │ │ │ ├── ToolbarUtil.kt │ │ │ │ ├── Debouncer.kt │ │ │ │ ├── ThrottleFirst.kt │ │ │ │ ├── BindingAdapter.kt │ │ │ │ ├── BiggerDotPasswordTransformationMethod.kt │ │ │ │ └── CalculateTime.kt │ │ │ ├── noticePost │ │ │ │ ├── timePicker │ │ │ │ │ └── adapter │ │ │ │ │ │ ├── DateViewHolder.kt │ │ │ │ │ │ ├── DateDayAdapter.kt │ │ │ │ │ │ ├── TimeDateAdapter.kt │ │ │ │ │ │ ├── DateMonthAdapter.kt │ │ │ │ │ │ ├── DateYearAdapter.kt │ │ │ │ │ │ ├── TimeMinuteAdapter.kt │ │ │ │ │ │ ├── TimeHourAdapter.kt │ │ │ │ │ │ └── TimeMeridiemAdapter.kt │ │ │ │ ├── NoticePostImageItemDecorator.kt │ │ │ │ ├── NoticePostImageAdapter.kt │ │ │ │ └── NoticePostViewModel.kt │ │ │ ├── noticeDetail │ │ │ │ ├── NoticeDetailModel.kt │ │ │ │ ├── NoticeDetailAdapter.kt │ │ │ │ └── NoticeDetailViewHolder.kt │ │ │ ├── signup │ │ │ │ ├── SchoolDepartmentListViewHolder.kt │ │ │ │ ├── SignUpActivity.kt │ │ │ │ ├── CreateAccountViewModel.kt │ │ │ │ ├── SchoolInputViewModel.kt │ │ │ │ ├── DepartmentInputViewModel.kt │ │ │ │ ├── CheckInfoActivity.kt │ │ │ │ └── SignupBottomSheetFragmentViewModel.kt │ │ │ ├── quickscan │ │ │ │ ├── QuickScanCompleteActivity.kt │ │ │ │ └── QuickScanAdapter.kt │ │ │ ├── entry │ │ │ │ └── EntryActivity.kt │ │ │ ├── home │ │ │ │ ├── HomeQuickscanItemDecorator.kt │ │ │ │ ├── HomeNoticeCategoryItemDecorator.kt │ │ │ │ ├── HomeNoticeContentItemDecorator.kt │ │ │ │ ├── HomeQuickscanViewHolder.kt │ │ │ │ ├── HomeNoticeContentAdapter.kt │ │ │ │ ├── HomeQuickscanAdapter.kt │ │ │ │ └── HomeNoticeContentViewHolder.kt │ │ │ ├── storage │ │ │ │ ├── StorageViewHolder.kt │ │ │ │ ├── StorageAdapter.kt │ │ │ │ └── StorageViewModel.kt │ │ │ ├── login │ │ │ │ ├── LoginBottomSheetFragment.kt │ │ │ │ ├── WelcomeActivity.kt │ │ │ │ └── LoginViewModel.kt │ │ │ ├── setting │ │ │ │ └── SettingViewModel.kt │ │ │ ├── MainActivity.kt │ │ │ └── SplashActivity.kt │ │ │ ├── data │ │ │ ├── datasource │ │ │ │ ├── SettingDataSource.kt │ │ │ │ ├── StorageDataSource.kt │ │ │ │ ├── LoginDataSource.kt │ │ │ │ ├── UserPreferencesDataSource.kt │ │ │ │ ├── QuickScanDataSource.kt │ │ │ │ ├── PostDataSource.kt │ │ │ │ ├── HomeDataSource.kt │ │ │ │ ├── NoticeDetailDataSource.kt │ │ │ │ └── SignUpDataSource.kt │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── RequestCheckEmailDto.kt │ │ │ │ │ ├── RequestDepartmentDto.kt │ │ │ │ │ ├── RequestQuickScanDto.kt │ │ │ │ │ └── RequestLoginDto.kt │ │ │ │ ├── response │ │ │ │ │ ├── ResponseLoginDto.kt │ │ │ │ │ ├── ResponseSettingDto.kt │ │ │ │ │ ├── ResponseNoticeAllDto.kt │ │ │ │ │ ├── ResponseStorageDto.kt │ │ │ │ │ ├── ResponseQuickScanDto.kt │ │ │ │ │ ├── ResponseNoticeQuickScanDto.kt │ │ │ │ │ └── ResponseNoticeDetailDto.kt │ │ │ │ └── BaseResponse.kt │ │ │ ├── mapper │ │ │ │ ├── ResponseSettingDtoMapper.kt │ │ │ │ ├── ResponseNoticeAllDtoMapper.kt │ │ │ │ ├── ResponseStorageDtoMapper.kt │ │ │ │ ├── ResponseQuickScanDtoMapper.kt │ │ │ │ └── NoticeDetailResponseDtoMapper.kt │ │ │ └── repositoryimpl │ │ │ │ ├── StorageRepositoryImpl.kt │ │ │ │ ├── LoginRepositoryImpl.kt │ │ │ │ ├── util │ │ │ │ └── extractErrorMessage.kt │ │ │ │ ├── SettingRepositoryImpl.kt │ │ │ │ ├── UserInfoRepositoryImpl.kt │ │ │ │ └── QuickScanRepositoryImpl.kt │ │ │ ├── UniVoiceApp.kt │ │ │ ├── core_ui │ │ │ ├── component │ │ │ │ └── SetStatusBarColor.kt │ │ │ ├── view │ │ │ │ ├── ViewExt.kt │ │ │ │ └── UiState.kt │ │ │ ├── base │ │ │ │ ├── BindingActivity.kt │ │ │ │ ├── BindingFragment.kt │ │ │ │ ├── BindingBottomSheetFragment.kt │ │ │ │ └── BindingDialogFragment.kt │ │ │ ├── theme │ │ │ │ └── Color.kt │ │ │ └── CustomSpinner.kt │ │ │ ├── data_remote │ │ │ ├── api │ │ │ │ ├── SettingApiService.kt │ │ │ │ ├── StorageApiService.kt │ │ │ │ ├── LoginApiService.kt │ │ │ │ ├── ApiKeyStorage.kt │ │ │ │ ├── PostApiService.kt │ │ │ │ ├── QuickScanApiService.kt │ │ │ │ ├── HomeApiService.kt │ │ │ │ └── NoticeDetailApiService.kt │ │ │ └── datasourceimpl │ │ │ │ ├── SettingDataSourceImpl.kt │ │ │ │ ├── StorageDataSourceImpl.kt │ │ │ │ ├── LoginDataSourceImpl.kt │ │ │ │ ├── PostDataSourceImpl.kt │ │ │ │ ├── QuickScanDataSourceImpl.kt │ │ │ │ ├── HomeDataSourceImpl.kt │ │ │ │ ├── NoticeDetailDataSourceImpl.kt │ │ │ │ └── SignUpDataSourceImpl.kt │ │ │ └── data_local │ │ │ ├── di │ │ │ └── ContentResolverModule.kt │ │ │ └── UserPreferencesDataSourceImpl.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── univoice │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── univoice │ │ └── ExampleInstrumentedTest.kt └── proguard-rules.pro ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── univoice-android-issue-template.md └── pull_request_template.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── icon ├── ic_date_arrow.svg └── ic_date_close.svg ├── settings.gradle.kts └── gradle.properties /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Team-UniVoice/AndroidUniVoice 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_app_icon-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/ic_app_icon-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/font/pretendard_bold.otf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/font/pretendard_regular.otf -------------------------------------------------------------------------------- /app/src/main/res/font/pretendard_semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/font/pretendard_semibold.otf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_app_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-hdpi/ic_app_icon.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_app_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-mdpi/ic_app_icon.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_app_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_app_icon.webp -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 160dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_app_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_app_icon.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_app_icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_app_icon.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_app_icon_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-hdpi/ic_app_icon_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_app_icon_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-mdpi/ic_app_icon_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_app_icon_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_app_icon_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_app_icon_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_app_icon_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_app_icon_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-hdpi/ic_app_icon_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_app_icon_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-mdpi/ic_app_icon_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_app_icon_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_app_icon_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_app_icon_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_app_icon_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_app_icon_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_app_icon_foreground.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_app_icon_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-UniVoice/UniVoice_Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_app_icon_foreground.webp -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/LoginRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | interface LoginRepository { 4 | suspend fun postLogin(email: String, password: String): Result 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/entity/HomeQuickScanListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.entity 2 | 3 | data class HomeQuickScanListEntity( 4 | val name: String, 5 | val count: Int, 6 | val image: String, 7 | ) -------------------------------------------------------------------------------- /icon/ic_date_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /icon/ic_date_close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_regular_fill_30_rect.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/SettingRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import com.univoice.domain.entity.SettingUserEntity 4 | 5 | interface SettingRepository { 6 | suspend fun getMyPage(): Result 7 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_blue50_fill_14_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray50_fill_10_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/Extension.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | fun String?.isJsonObject(): Boolean = this?.startsWith("{") == true && this.endsWith("}") 4 | fun String?.isJsonArray(): Boolean = this?.startsWith("[") == true && this.endsWith("]") 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/StorageRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import com.univoice.domain.entity.StorageListEntity 4 | 5 | interface StorageRepository { 6 | suspend fun getSaves(): Result?> 7 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_blue300_fill_30_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray200_fill_10_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_mint400_fill_10_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/checkbox_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jun 27 14:50:18 KST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shpae_mint50_fill_40_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray200_fill_20_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray200_fill_40_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray200_fill_90_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_mint400_fill_20_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_mint400_fill_24_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_mint400_fill_30_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_mint400_fill_40_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_mint900_fill_50_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/util/ToolbarUtil.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.util 2 | 3 | import android.app.Activity 4 | import android.widget.ImageButton 5 | 6 | fun Activity.setupToolbarClickListener(button: ImageButton) { 7 | button.setOnClickListener { 8 | finish() 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/res/font/font_pretendard_bold.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/univoice-android-issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: UniVoice android issue template 3 | about: Describe this issue template's purpose here. 4 | title: "[FEAT] : " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 📌𝗧𝗮𝘀𝗸 11 | - [ ] 12 | - [ ] 13 | 14 | ## 💡𝗥𝗲𝗳𝗲𝗿𝗲𝗻𝗰𝗲 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/SettingDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseSettingDto 5 | 6 | interface SettingDataSource { 7 | suspend fun getMyPage(): BaseResponse 8 | } -------------------------------------------------------------------------------- /app/src/main/res/font/font_pretendard_regular.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/Qualifier.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.BINARY) 7 | annotation class UniVoiceRetrofit 8 | 9 | @Qualifier 10 | @Retention(AnnotationRetention.BINARY) 11 | annotation class AccessToken 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/StorageDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseStorageDto 5 | 6 | interface StorageDataSource { 7 | suspend fun getSaves(): BaseResponse> 8 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_date_day_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/font/font_pretendard_semibold.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/sel_color_bnv_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_app_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/request/RequestCheckEmailDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class RequestCheckEmailDto( 8 | @SerialName("email") 9 | val email: String 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/entity/NoticeListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.entity 2 | 3 | data class NoticeListEntity( 4 | val id: Int, 5 | val title: String, 6 | val likeCount: Int, 7 | val viewCount: Int, 8 | val category: String, 9 | val date: String, 10 | val image: String, 11 | ) -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_app_icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseLoginDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseLoginDto( 8 | @SerialName("accessToken") 9 | val accessToken: String = "", 10 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gradient_fill_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_regular_line_16_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/entity/StorageListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.entity 2 | 3 | data class StorageListEntity ( 4 | val id: Int, 5 | val title: String, 6 | val viewCount: Int, 7 | val noticeLike: Int, 8 | val category: String, 9 | val createdAt: String, 10 | val image: String? 11 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_regular_line_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/request/RequestDepartmentDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class RequestDepartmentDto( 8 | @SerialName("universityName") 9 | val universityName: String = "" 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_white_fill_top25_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/request/RequestQuickScanDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import java.io.Serial 6 | 7 | @Serializable 8 | data class RequestQuickScanDto( 9 | @SerialName("affiliation") val affiliation: String = "" 10 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray50_line_12_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_home_nav_selected_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_quick_scan_tab_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray900_fill_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/entity/SettingUserEntity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.entity 2 | 3 | data class SettingUserEntity( 4 | val id: Int, 5 | val name: String, 6 | val collegeDepartment: String, 7 | val department: String, 8 | val admissionNumber: String, 9 | val university: String, 10 | val universityLogoImage: String, 11 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_date_day_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_login_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_setting_nav_selected_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_signup_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_storage_nav_selected_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/QuickScanRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import com.univoice.domain.entity.QuickScanListEntity 4 | 5 | interface QuickScanRepository { 6 | suspend fun postQuickScan(writeAffiliation: String): Result?> 7 | suspend fun postQuickScanViewCheck(noticeId: Int): Result 8 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_createaccount_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_gray100_fill_8_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_regular_line_4_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_white_line_30_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_white_line_50_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_white_line_40_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/request/RequestLoginDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.request 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class RequestLoginDto( 8 | @SerialName("email") 9 | val email: String = "", 10 | @SerialName("password") 11 | val password: String = "", 12 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_spinner_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_spinner_up.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/LoginDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.request.RequestLoginDto 5 | import com.univoice.data.dto.response.ResponseLoginDto 6 | 7 | interface LoginDataSource { 8 | suspend fun postLogin(requestLoginDto: RequestLoginDto): BaseResponse 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/mapper/ResponseSettingDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.mapper 2 | 3 | import com.univoice.data.dto.response.ResponseSettingDto 4 | import com.univoice.domain.entity.SettingUserEntity 5 | 6 | fun ResponseSettingDto.toSettingUserEntity() = SettingUserEntity( 7 | id, name, collegeDepartment, department, admissionNumber, university, universityLogoImage, 8 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_notice_detail_like.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/DateViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import androidx.annotation.Keep 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.univoice.databinding.ItemDateNumberBinding 6 | 7 | @Keep 8 | class DateViewHolder(val binding: ItemDateNumberBinding) : 9 | RecyclerView.ViewHolder(binding.root) -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_notice_detail_bookmark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sel_quick_scan_bookmark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class BaseResponse( 8 | @SerialName("status") 9 | val status: Int, 10 | @SerialName("message") 11 | val message: String, 12 | @SerialName("data") 13 | val data: T? = null, 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/PostRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import java.io.File 4 | 5 | interface PostRepository { 6 | suspend fun postSignUp( 7 | title: String, 8 | content: String, 9 | target: String?, 10 | startTime: String?, 11 | endTime: String?, 12 | noticeImages: List?, 13 | ): Result 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_box.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cursor.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/spinner_array.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 학번 선택하기 5 | 24학번 6 | 23학번 7 | 22학번 8 | 21학번 9 | 20학번 10 | 19학번 11 | 18학번 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/mapper/ResponseNoticeAllDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.mapper 2 | 3 | import com.univoice.data.dto.response.ResponseNoticeAllDto 4 | import com.univoice.domain.entity.NoticeListEntity 5 | 6 | fun ResponseNoticeAllDto.toNoticeListEntity() = NoticeListEntity( 7 | id, 8 | title, 9 | likeCount, 10 | viewCount, 11 | category, 12 | createdAt, 13 | image ?: "" 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/mapper/ResponseStorageDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.mapper 2 | 3 | import com.univoice.data.dto.response.ResponseStorageDto 4 | import com.univoice.domain.entity.StorageListEntity 5 | 6 | fun ResponseStorageDto.toStorageListEntity() = StorageListEntity( 7 | id, 8 | title, 9 | viewCount, 10 | noticeLike, 11 | category, 12 | createdAt, 13 | image ?: "" 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/UniVoiceApp.kt: -------------------------------------------------------------------------------- 1 | package com.univoice 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | import timber.log.Timber 6 | 7 | @HiltAndroidApp 8 | class UniVoiceApp : Application() { 9 | override fun onCreate() { 10 | super.onCreate() 11 | setTimber() 12 | } 13 | 14 | private fun setTimber() { 15 | Timber.plant(Timber.DebugTree()) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_box_checked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/UserPreferencesDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface UserPreferencesDataSource { 6 | suspend fun saveUserAccessToken(accessToken: String) 7 | fun getUserAccessToken(): Flow 8 | 9 | suspend fun saveCheckLogin(checkLogin: Boolean) 10 | fun getCheckLogin(): Flow 11 | 12 | suspend fun clear() 13 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/test/java/com/univoice/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.univoice 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/main/java/com/univoice/domain/repository/UserInfoRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface UserInfoRepository { 6 | suspend fun saveUserAccessToken(accessToken: String) 7 | 8 | fun getUserAccessToken(): Flow 9 | 10 | suspend fun saveCheckLogin(checkLogin: Boolean) 11 | 12 | fun getCheckLogin(): Flow 13 | 14 | suspend fun clear() 15 | } -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## ✅ 𝗖𝗵𝗲𝗰𝗸-𝗟𝗶𝘀𝘁 2 | - merge할 브랜치의 위치를 확인해 주세요.(main❌/develop⭕) 3 | - 리뷰가 필요한 경우 리뷰어를 지정해 주세요. 4 | - 리뷰는 PR이 올라오면 최대한 빠르게 진행합니다. 5 | - P1 단계의 리뷰는 빠르게 확인 후 반영합니다. 6 | - Approve된 PR은 assigner가 머지하고, 수정 요청이 온 경우 수정 후 다시 push를 합니다. 7 | 8 | ## 📌 𝗜𝘀𝘀𝘂𝗲𝘀 9 | - closed # 10 | 11 | ## 📎 𝗪𝗼𝗿𝗸 𝗗𝗲𝘀𝗰𝗿𝗶𝗽𝘁𝗶𝗼𝗻 12 | - 13 | - 14 | 15 | ## 📷 𝗦𝗰𝗿𝗲𝗲𝗻𝘀𝗵𝗼𝘁 16 | 17 | 18 | ## 💬 𝗧𝗼 𝗥𝗲𝘃𝗶𝗲𝘄𝗲𝗿𝘀 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticeDetail/NoticeDetailModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticeDetail 2 | 3 | data class NoticeDetailModel( 4 | val noticeId: Int, 5 | val writeAffiliation: String, 6 | val title: String, 7 | val target: String, 8 | val startTime: String, 9 | val endTime: String, 10 | val imageList: List, 11 | val content: String, 12 | val createdAt: String, 13 | val viewCount: Int 14 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_student_id_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_date_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/QuickScanDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.request.RequestQuickScanDto 5 | import com.univoice.data.dto.response.ResponseQuickScanDto 6 | 7 | interface QuickScanDataSource { 8 | suspend fun postQuickScan(requestQuickScanDto: RequestQuickScanDto): BaseResponse> 9 | suspend fun postQuickScanViewCheck(noticeId: Int): BaseResponse 10 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_date_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/mapper/ResponseQuickScanDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.mapper 2 | 3 | import com.univoice.data.dto.response.ResponseQuickScanDto 4 | import com.univoice.domain.entity.QuickScanListEntity 5 | 6 | fun ResponseQuickScanDto.toQuickScanListEntity() = QuickScanListEntity( 7 | id, 8 | startTime, 9 | endTime, 10 | title, 11 | target, 12 | writeAffiliation, 13 | contentSummary, 14 | likeCount, 15 | viewCount, 16 | category, 17 | createdAt 18 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_date_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_detail_bookmark_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_signup_right_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_post_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_post_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_handle.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_detail_bookmark_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_box_unchecked.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_quick_scan_bookmark_selected.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/PostDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import okhttp3.MultipartBody 5 | import okhttp3.RequestBody 6 | 7 | interface PostDataSource { 8 | suspend fun postNotice( 9 | title: RequestBody, 10 | content: RequestBody, 11 | target: RequestBody?, 12 | startTime: RequestBody?, 13 | endTime: RequestBody?, 14 | noticeImages: List?, 15 | ): BaseResponse 16 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bottomsheet_notice_post_target_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_quick_scan_bookmark_unselected.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/component/SetStatusBarColor.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_ui.component 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.SideEffect 5 | import androidx.compose.ui.graphics.Color 6 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 7 | 8 | @Composable 9 | fun SetStatusBarColor(color: Color) { 10 | val systemUiController = rememberSystemUiController() 11 | SideEffect { 12 | systemUiController.setSystemBarsColor(color) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/SettingApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseSettingDto 5 | import com.univoice.data_remote.api.ApiKeyStorage.API 6 | import com.univoice.data_remote.api.ApiKeyStorage.MYPAGE 7 | import com.univoice.data_remote.api.ApiKeyStorage.V1 8 | import retrofit2.http.GET 9 | 10 | interface SettingApiService { 11 | @GET("/$API/$V1/$MYPAGE") 12 | suspend fun getMyPage(): BaseResponse 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/view/ViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_ui.view 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | class ItemDiffCallback( 6 | val onItemsTheSame: (T, T) -> Boolean, 7 | val onContentsTheSame: (T, T) -> Boolean 8 | ) : DiffUtil.ItemCallback() { 9 | override fun areItemsTheSame( 10 | oldItem: T, newItem: T 11 | ): Boolean = onItemsTheSame(oldItem, newItem) 12 | 13 | override fun areContentsTheSame( 14 | oldItem: T, newItem: T 15 | ): Boolean = onContentsTheSame(oldItem, newItem) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/entity/QuickScanListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.entity 2 | 3 | data class QuickScanListEntity( 4 | val id: Int, 5 | val startTime: String? = null, 6 | val endTime: String? = null, 7 | val title: String = "", 8 | val target: String? = null, 9 | val writeAffiliation: String = "", 10 | val contentSummary: String = "", 11 | val likeCount: Int = 0, 12 | var viewCount: Int = 0, 13 | val category: String = "", 14 | val createdAt: String = "", 15 | val logoImage: String? = null, 16 | var saveCheck: Boolean = false 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/util/Debouncer.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.util 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.Job 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | 9 | class Debouncer { 10 | private var debounceJob: Job? = null 11 | fun setDelay(value: T, delay: Long, action: (T) -> Unit) { 12 | debounceJob?.cancel() 13 | debounceJob = GlobalScope.launch(Dispatchers.Main) { 14 | delay(delay) 15 | action(value) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_signup_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/util/ThrottleFirst.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.util 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.flow 5 | 6 | fun Flow.throttleFirst(periodMillis: Long): Flow { 7 | require(periodMillis > 0) { "period should be positive" } 8 | return flow { 9 | var lastTime = 0L 10 | collect { value -> 11 | val currentTime = System.currentTimeMillis() 12 | if (currentTime - lastTime >= periodMillis) { 13 | lastTime = currentTime 14 | emit(value) 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nav_storage_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/mapper/NoticeDetailResponseDtoMapper.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.mapper 2 | 3 | import com.univoice.data.dto.response.ResponseNoticeDetailDto 4 | import com.univoice.domain.entity.NoticeDetailEntity 5 | 6 | fun ResponseNoticeDetailDto.toNoticeDetailEntity() = NoticeDetailEntity( 7 | id, 8 | title, 9 | content, 10 | noticeLike, 11 | viewCount, 12 | target, 13 | startTime, 14 | endTime, 15 | category, 16 | contentSummary, 17 | memberId, 18 | writeAffiliation, 19 | noticeImages, 20 | createdAt, 21 | likeCheck, 22 | saveCheck, 23 | dayOfWeek 24 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nav_storage_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | rootProject.name = "UniVoice" 23 | include(":app") 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/SettingDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.SettingDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.response.ResponseSettingDto 6 | import com.univoice.data_remote.api.SettingApiService 7 | import javax.inject.Inject 8 | 9 | class SettingDataSourceImpl @Inject constructor( 10 | private val settingApiService: SettingApiService 11 | ) : SettingDataSource { 12 | override suspend fun getMyPage(): BaseResponse = 13 | settingApiService.getMyPage() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/SchoolDepartmentListViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import android.view.View 4 | import android.widget.TextView 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.univoice.R 7 | 8 | class SchoolDepartmentListViewHolder(itemView: View, private val itemClickListener: ((Int) -> Unit)?) : 9 | RecyclerView.ViewHolder(itemView) { 10 | val textView: TextView = itemView.findViewById(R.id.tv_listview_item) 11 | 12 | init { 13 | itemView.setOnClickListener { 14 | itemClickListener?.invoke(adapterPosition) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/StorageDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.StorageDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.response.ResponseStorageDto 6 | import com.univoice.data_remote.api.StorageApiService 7 | import javax.inject.Inject 8 | 9 | class StorageDataSourceImpl @Inject constructor( 10 | private val storageApiService: StorageApiService 11 | ) : StorageDataSource { 12 | override suspend fun getSaves(): BaseResponse> = 13 | storageApiService.getSaves() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import com.univoice.domain.entity.NoticeListEntity 4 | import com.univoice.domain.entity.HomeQuickScanListEntity 5 | 6 | interface HomeRepository { 7 | suspend fun getNoticeQuickScan(): Result?> 8 | 9 | suspend fun getNoticeAll(): Result?> 10 | 11 | suspend fun getNoticeUniversity(): Result?> 12 | 13 | suspend fun getNoticeCollege(): Result?> 14 | 15 | suspend fun getNoticeDepartment(): Result?> 16 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/entity/NoticeDetailEntity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.entity 2 | 3 | 4 | data class NoticeDetailEntity( 5 | val id: Int, 6 | val title: String, 7 | val content: String, 8 | val noticeLike: Int, 9 | val viewCount: Int, 10 | val target: String?, 11 | val startTime: String?, 12 | val endTime: String?, 13 | val category: String, 14 | val contentSummary: String, 15 | val memberId: Int, 16 | val writeAffiliation: String, 17 | val noticeImages: List, 18 | val createdAt: String, 19 | val likeCheck: Boolean, 20 | val saveCheck: Boolean, 21 | val dayOfWeek: String 22 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/NoticeDetailRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import com.univoice.domain.entity.NoticeDetailEntity 4 | 5 | interface NoticeDetailRepository { 6 | suspend fun getNoticeDetail(noticeId: Int): Result 7 | 8 | suspend fun postNoticeLike(noticeId: Int): Result 9 | 10 | suspend fun postNoticeCancelLike(noticeId: Int): Result 11 | 12 | suspend fun postNoticeDetailViewCount(noticeId: Int): Result 13 | 14 | suspend fun postNoticeDetailSave(noticeId: Int): Result 15 | 16 | suspend fun postNoticeDetailCancel(noticeId: Int): Result 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/StorageApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseStorageDto 5 | import com.univoice.data_remote.api.ApiKeyStorage.ALL 6 | import com.univoice.data_remote.api.ApiKeyStorage.API 7 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE 8 | import com.univoice.data_remote.api.ApiKeyStorage.SAVE 9 | import com.univoice.data_remote.api.ApiKeyStorage.V1 10 | import retrofit2.http.GET 11 | 12 | interface StorageApiService { 13 | @GET("$API/$V1/$NOTICE/$SAVE/$ALL") 14 | suspend fun getSaves(): BaseResponse> 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_local/di/ContentResolverModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_local.di 2 | 3 | import android.content.ContentResolver 4 | import android.content.Context 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object ContentResolverModule { 15 | @Provides 16 | @Singleton 17 | fun providesContentResolver( 18 | @ApplicationContext context: Context, 19 | ): ContentResolver = context.contentResolver 20 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseSettingDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseSettingDto( 8 | @SerialName("id") val id: Int = -1, 9 | @SerialName("name") val name: String = "", 10 | @SerialName("collegeDepartment") val collegeDepartment: String = "", 11 | @SerialName("department") val department: String = "", 12 | @SerialName("admissionNumber") val admissionNumber: String = "", 13 | @SerialName("university") val university: String = "", 14 | @SerialName("universityLogoImage") val universityLogoImage: String = "", 15 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/LoginDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.LoginDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.request.RequestLoginDto 6 | import com.univoice.data.dto.response.ResponseLoginDto 7 | import com.univoice.data_remote.api.LoginApiService 8 | import javax.inject.Inject 9 | 10 | class LoginDataSourceImpl @Inject constructor( 11 | private val loginApiService: LoginApiService 12 | ) : LoginDataSource { 13 | override suspend fun postLogin(requestLoginDto: RequestLoginDto): BaseResponse = 14 | loginApiService.postLogin(requestLoginDto) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/util/BindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.util 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import coil.load 6 | import coil.transform.RoundedCornersTransformation 7 | import com.univoice.R 8 | 9 | @BindingAdapter("imageUrl") 10 | fun loadImage(view: ImageView, url: String?) { 11 | view.load(url) { 12 | placeholder(R.drawable.img_quick_scan_placeholder) 13 | error(R.drawable.img_quick_scan_placeholder) 14 | } 15 | } 16 | 17 | @BindingAdapter("setCircleImage") 18 | fun ImageView.setCircleImage(img: String?) { 19 | load(img) { 20 | transformations(RoundedCornersTransformation(1000f)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/repositoryimpl/StorageRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.repositoryimpl 2 | 3 | import com.univoice.data.datasource.StorageDataSource 4 | import com.univoice.data.mapper.toStorageListEntity 5 | import com.univoice.domain.entity.StorageListEntity 6 | import com.univoice.domain.repository.StorageRepository 7 | import javax.inject.Inject 8 | 9 | class StorageRepositoryImpl @Inject constructor( 10 | private val storageDataSource: StorageDataSource 11 | ) : StorageRepository { 12 | override suspend fun getSaves(): Result?> { 13 | return runCatching { 14 | storageDataSource.getSaves().data?.map { it.toStorageListEntity() } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/HomeDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseNoticeAllDto 5 | import com.univoice.data.dto.response.ResponseNoticeQuickScanDto 6 | 7 | interface HomeDataSource { 8 | suspend fun getNoticeQuickScan(): BaseResponse 9 | 10 | suspend fun getNoticeAll(): BaseResponse> 11 | 12 | suspend fun getNoticeUniversity(): BaseResponse> 13 | 14 | suspend fun getNoticeCollege(): BaseResponse> 15 | 16 | suspend fun getNoticeDepartment(): BaseResponse> 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/NoticeDetailDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseNoticeDetailDto 5 | 6 | interface NoticeDetailDataSource { 7 | suspend fun getNoticeDetail(noticeId: Int): BaseResponse 8 | 9 | suspend fun postNoticeLike(noticeId: Int): BaseResponse 10 | 11 | suspend fun postNoticeCancelLike(noticeId: Int): BaseResponse 12 | 13 | suspend fun postNoticeDetailViewCount(noticeId: Int): BaseResponse 14 | 15 | suspend fun postNoticeDetailSave(noticeId: Int): BaseResponse 16 | 17 | suspend fun postNoticeDetailCancel(noticeId: Int): BaseResponse 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/LoginApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.request.RequestLoginDto 5 | import com.univoice.data.dto.response.ResponseLoginDto 6 | import com.univoice.data_remote.api.ApiKeyStorage.API 7 | import com.univoice.data_remote.api.ApiKeyStorage.AUTH 8 | import com.univoice.data_remote.api.ApiKeyStorage.SIGNIN 9 | import com.univoice.data_remote.api.ApiKeyStorage.V1 10 | import retrofit2.http.Body 11 | import retrofit2.http.POST 12 | 13 | interface LoginApiService { 14 | @POST("/$API/$V1/$AUTH/$SIGNIN") 15 | suspend fun postLogin( 16 | @Body requestLoginDto: RequestLoginDto, 17 | ): BaseResponse 18 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_floating_action_btn.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/repositoryimpl/LoginRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.repositoryimpl 2 | 3 | import com.univoice.data.datasource.LoginDataSource 4 | import com.univoice.data.dto.request.RequestLoginDto 5 | import com.univoice.domain.repository.LoginRepository 6 | import timber.log.Timber 7 | import javax.inject.Inject 8 | 9 | class LoginRepositoryImpl @Inject constructor( 10 | private val loginDataSource: LoginDataSource 11 | ) : LoginRepository { 12 | override suspend fun postLogin(email: String, password: String): Result { 13 | return runCatching { 14 | val result = loginDataSource.postLogin(RequestLoginDto(email, password)) 15 | Timber.d(result.message) 16 | result.data?.accessToken 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/repositoryimpl/util/extractErrorMessage.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.repositoryimpl.util 2 | 3 | import okhttp3.ResponseBody 4 | import org.json.JSONException 5 | import org.json.JSONObject 6 | import retrofit2.HttpException 7 | 8 | fun HttpException.extractErrorMessage(): String { 9 | if (response()?.code() == 413) return "이미지는 3mb를 넘을 수 없습니다" 10 | 11 | val errorBody: ResponseBody? = response()?.errorBody() 12 | if (errorBody != null) { 13 | val error = errorBody.string() 14 | return try { 15 | val jsonObject = JSONObject(error) 16 | jsonObject.getString("message") 17 | } catch (e: JSONException) { 18 | "알수 없는 오류가 발생 했습니다" 19 | } 20 | } 21 | return "알수 없는 오류가 발생 했습니다" 22 | } 23 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/univoice/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.univoice 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.univoice", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/base/BindingActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_ui.base 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.LayoutRes 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.databinding.ViewDataBinding 8 | 9 | abstract class BindingActivity( 10 | @LayoutRes private val layoutRes: Int, 11 | ) : AppCompatActivity() { 12 | protected lateinit var binding: T 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = DataBindingUtil.setContentView(this, layoutRes) 17 | binding.lifecycleOwner = this 18 | initView() 19 | } 20 | 21 | protected abstract fun initView() 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseNoticeAllDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseNoticeAllDto( 8 | @SerialName("id") 9 | val id: Int, 10 | @SerialName("startTime") 11 | val startTime: String? = "", 12 | @SerialName("endTime") 13 | val endTime: String? = "", 14 | @SerialName("title") 15 | val title: String, 16 | @SerialName("likeCount") 17 | val likeCount: Int, 18 | @SerialName("viewCount") 19 | val viewCount: Int, 20 | @SerialName("category") 21 | val category: String, 22 | @SerialName("createdAt") 23 | val createdAt: String, 24 | @SerialName("image") 25 | val image: String? = "", 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseStorageDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseStorageDto( 8 | @SerialName("id") val id: Int = -1, 9 | @SerialName("title") val title: String = "", 10 | @SerialName("viewCount") val viewCount: Int = 0, 11 | @SerialName("noticeLike") val noticeLike: Int = 0, 12 | @SerialName("category") val category: String = "", 13 | @SerialName("startTime") val startTime: String? = null, 14 | @SerialName("endTime") val endTime: String? = null, 15 | @SerialName("createdAt") val createdAt: String = "", 16 | @SerialName("updatedAt") val updatedAt: String = "", 17 | @SerialName("image") val image: String? = null 18 | ) -------------------------------------------------------------------------------- /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/main/java/com/univoice/core_ui/view/UiState.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_ui.view 2 | 3 | sealed interface UiState { 4 | data object Empty : UiState 5 | 6 | data object Loading : UiState 7 | 8 | data class Success( 9 | val data: T, 10 | ) : UiState 11 | 12 | data class Failure( 13 | val msg: String, 14 | ) : UiState 15 | 16 | fun getUiStateModel(): UiStateModel { 17 | return UiStateModel( 18 | this is Empty, 19 | this is Loading, 20 | this is Success, 21 | this is Failure 22 | ) 23 | } 24 | } 25 | 26 | data class UiStateModel( 27 | val isEmpty: Boolean = false, 28 | val isLoading: Boolean = true, 29 | val isSuccess: Boolean = false, 30 | val isFailure: Boolean = false 31 | ) -------------------------------------------------------------------------------- /app/src/main/res/layout/item_date_number.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nav_home_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nav_home_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/PostDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.PostDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data_remote.api.PostApiService 6 | import okhttp3.MultipartBody 7 | import okhttp3.RequestBody 8 | import javax.inject.Inject 9 | 10 | class PostDataSourceImpl @Inject constructor( 11 | private val postApiService: PostApiService 12 | ) : PostDataSource { 13 | override suspend fun postNotice( 14 | title: RequestBody, 15 | content: RequestBody, 16 | target: RequestBody?, 17 | startTime: RequestBody?, 18 | endTime: RequestBody?, 19 | noticeImages: List? 20 | ): BaseResponse { 21 | return postApiService.postNotice(title, content, target, startTime, endTime, noticeImages) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/quickscan/QuickScanCompleteActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.quickscan 2 | 3 | import android.content.Intent 4 | import com.univoice.R 5 | import com.univoice.core_ui.base.BindingActivity 6 | import com.univoice.databinding.ActivityQuickScanCompleteBinding 7 | import com.univoice.feature.MainActivity 8 | 9 | class QuickScanCompleteActivity : 10 | BindingActivity(R.layout.activity_quick_scan_complete) { 11 | override fun initView() { 12 | initConfirmBtnClickListener() 13 | } 14 | 15 | private fun initConfirmBtnClickListener() { 16 | binding.btnQuickScanCompleteConfirm.setOnClickListener { 17 | val intent = Intent(this, MainActivity::class.java) 18 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK 19 | startActivity(intent) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/repositoryimpl/SettingRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.repositoryimpl 2 | 3 | import com.univoice.data.datasource.SettingDataSource 4 | import com.univoice.data.mapper.toSettingUserEntity 5 | import com.univoice.domain.entity.SettingUserEntity 6 | import com.univoice.domain.repository.SettingRepository 7 | import javax.inject.Inject 8 | 9 | class SettingRepositoryImpl @Inject constructor( 10 | private val settingDataSource: SettingDataSource 11 | ) : SettingRepository { 12 | override suspend fun getMyPage(): Result { 13 | return runCatching { 14 | settingDataSource.getMyPage().data?.toSettingUserEntity() ?: SettingUserEntity( 15 | -1, 16 | "", 17 | "", 18 | "", 19 | "", 20 | "", 21 | "", 22 | ) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_post_image_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | 12 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/UserPreferencesModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.preferencesDataStore 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object UserPreferencesModule { 17 | private const val PREFERENCE_NAME = "user_preferences" 18 | private val Context.dataStore by preferencesDataStore(name = PREFERENCE_NAME) 19 | 20 | @Provides 21 | @Singleton 22 | fun provideDataStore( 23 | @ApplicationContext context: Context 24 | ): DataStore = context.dataStore 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/QuickScanDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.QuickScanDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.request.RequestQuickScanDto 6 | import com.univoice.data.dto.response.ResponseQuickScanDto 7 | import com.univoice.data_remote.api.QuickScanApiService 8 | import javax.inject.Inject 9 | 10 | class QuickScanDataSourceImpl @Inject constructor( 11 | private val quickScanApiService: QuickScanApiService 12 | ) : QuickScanDataSource { 13 | override suspend fun postQuickScan(requestQuickScanDto: RequestQuickScanDto): BaseResponse> = 14 | quickScanApiService.postQuickScan(requestQuickScanDto) 15 | 16 | override suspend fun postQuickScanViewCheck(noticeId: Int): BaseResponse = 17 | quickScanApiService.postQuickScanViewCheck(noticeId) 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_bottom_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/ApiKeyStorage.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | object ApiKeyStorage { 4 | const val API = "api" 5 | const val V1 = "v1" 6 | const val AUTH = "auth" 7 | const val SIGNIN = "signin" 8 | const val NOTICE = "notice" 9 | const val ALL = "all" 10 | const val QUICKHEAD = "quickhead" 11 | const val UNIVERSITY_DATA = "universityData" 12 | const val UNIVERSITY = "university" 13 | const val SAVE = "save" 14 | const val QUICK = "quick" 15 | const val COLLEGE_DEPARTMENT = "college-department" 16 | const val DEPARTMENT = "department" 17 | const val VIEW_CHECK = "view-check" 18 | const val NOTICE_ID = "noticeId" 19 | const val MYPAGE = "mypage" 20 | const val VIEW_COUNT = "view-count" 21 | const val CANCEL = "cancel" 22 | const val CHECK_EMAIL = "check-email" 23 | const val SIGNUP = "signup" 24 | const val LIKE = "like" 25 | const val CREATE = "create" 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/repositoryimpl/UserInfoRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.repositoryimpl 2 | 3 | import com.univoice.data.datasource.UserPreferencesDataSource 4 | import com.univoice.domain.repository.UserInfoRepository 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class UserInfoRepositoryImpl @Inject constructor( 9 | private val dataSource: UserPreferencesDataSource 10 | ) : UserInfoRepository { 11 | 12 | override suspend fun saveUserAccessToken(accessToken: String) { 13 | dataSource.saveUserAccessToken(accessToken) 14 | } 15 | 16 | override fun getUserAccessToken(): Flow = dataSource.getUserAccessToken() 17 | 18 | override suspend fun saveCheckLogin(checkLogin: Boolean) { 19 | dataSource.saveCheckLogin(checkLogin) 20 | } 21 | 22 | override fun getCheckLogin(): Flow = dataSource.getCheckLogin() 23 | 24 | override suspend fun clear() = dataSource.clear() 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/interceptor/TokenInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.interceptor 2 | 3 | import com.univoice.data.datasource.UserPreferencesDataSource 4 | import kotlinx.coroutines.flow.first 5 | import kotlinx.coroutines.runBlocking 6 | import okhttp3.Interceptor 7 | import okhttp3.Response 8 | import timber.log.Timber 9 | import javax.inject.Inject 10 | 11 | class TokenInterceptor @Inject 12 | constructor( 13 | private val userPreferencesDataSource: UserPreferencesDataSource, 14 | ) : Interceptor { 15 | override fun intercept(chain: Interceptor.Chain): Response = runBlocking { 16 | var accessToken = userPreferencesDataSource.getUserAccessToken().first() 17 | 18 | val request = chain.request().newBuilder() 19 | .addHeader("Authorization", "Bearer $accessToken") 20 | .build() 21 | 22 | val response = chain.proceed(request) 23 | Timber.tag("interceptor").d("accessToken $accessToken") 24 | 25 | response 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/domain/repository/SignUpRepository.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.domain.repository 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.request.RequestCheckEmailDto 5 | import com.univoice.data.dto.request.RequestDepartmentDto 6 | import okhttp3.MultipartBody 7 | import okhttp3.RequestBody 8 | import java.io.File 9 | 10 | interface SignUpRepository { 11 | suspend fun postUniversityNames(): Result> 12 | suspend fun postDepartments(requestDepartmentDto: RequestDepartmentDto): Result>> 13 | suspend fun postEmail(requestCheckEmailDto: RequestCheckEmailDto): Result> 14 | suspend fun postSignUp( 15 | admissionNumber: String, 16 | name: String, 17 | studentNumber: String, 18 | email: String, 19 | password: String, 20 | universityName: String, 21 | departmentName: String, 22 | studentCardImage: File 23 | ): Result 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_views.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_quick_scan_placeholder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/entry/EntryActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.entry 2 | 3 | import android.content.Intent 4 | import com.univoice.R 5 | import com.univoice.core_ui.base.BindingActivity 6 | import com.univoice.databinding.ActivityEntryBinding 7 | import com.univoice.feature.login.LoginActivity 8 | import com.univoice.feature.signup.SignUpActivity 9 | 10 | class EntryActivity : BindingActivity(R.layout.activity_entry) { 11 | override fun initView() { 12 | initLoginBtnClickListener() 13 | initSignupBtnClickListener() 14 | } 15 | 16 | private fun initSignupBtnClickListener() { 17 | binding.btnEntrySignup.setOnClickListener { 18 | startActivity(Intent(this, SignUpActivity::class.java)) 19 | } 20 | } 21 | 22 | private fun initLoginBtnClickListener() { 23 | binding.btnEntryLogin.setOnClickListener { 24 | startActivity(Intent(this, LoginActivity::class.java)) 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/PostApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data_remote.api.ApiKeyStorage.API 5 | import com.univoice.data_remote.api.ApiKeyStorage.CREATE 6 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE 7 | import com.univoice.data_remote.api.ApiKeyStorage.V1 8 | import okhttp3.MultipartBody 9 | import okhttp3.RequestBody 10 | import retrofit2.http.Multipart 11 | import retrofit2.http.POST 12 | import retrofit2.http.Part 13 | 14 | interface PostApiService { 15 | @Multipart 16 | @POST("/$API/$V1/$NOTICE/$CREATE") 17 | suspend fun postNotice( 18 | @Part("title") title: RequestBody, 19 | @Part("content") content: RequestBody, 20 | @Part("target") target: RequestBody?, 21 | @Part("startTime") startTime: RequestBody?, 22 | @Part("endTime") endTime: RequestBody?, 23 | @Part noticeImages: List?, 24 | ): BaseResponse 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/SignUpActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import android.content.Intent 4 | import com.univoice.R 5 | import com.univoice.core_ui.base.BindingActivity 6 | import com.univoice.databinding.ActivitySignupBinding 7 | import com.univoice.feature.util.setupToolbarClickListener 8 | 9 | class SignUpActivity : BindingActivity(R.layout.activity_signup) { 10 | override fun initView() { 11 | initStartBtnClickListener() 12 | initToolbar() 13 | } 14 | 15 | private fun initToolbar() { 16 | with(binding.toolbarSignup) { 17 | tvToolbarTitle.text = applicationContext.getString(R.string.tv_toolbar_signup_title) 18 | setupToolbarClickListener(ibToolbarIcon) 19 | } 20 | } 21 | 22 | private fun initStartBtnClickListener() { 23 | binding.btnSignupStart.setOnClickListener { 24 | startActivity(Intent(this, SchoolInputActivity::class.java)) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/datasource/SignUpDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.datasource 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.request.RequestCheckEmailDto 5 | import com.univoice.data.dto.request.RequestDepartmentDto 6 | import okhttp3.MultipartBody 7 | import okhttp3.RequestBody 8 | 9 | interface SignUpDataSource { 10 | suspend fun postUniversityNames(): BaseResponse> 11 | suspend fun postDepartments(requestDepartmentDto: RequestDepartmentDto): BaseResponse> 12 | suspend fun postEmail(requestCheckEmailDto: RequestCheckEmailDto): BaseResponse 13 | suspend fun postSignUp( 14 | admissionNumber: RequestBody, 15 | name: RequestBody, 16 | studentNumber: RequestBody, 17 | email: RequestBody, 18 | password: RequestBody, 19 | universityName: RequestBody, 20 | departmentName: RequestBody, 21 | studentCardImage: MultipartBody.Part, 22 | ): BaseResponse 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseQuickScanDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseQuickScanDto( 8 | @SerialName("id") val id: Int = -1, 9 | @SerialName("startTime") val startTime: String? = null, 10 | @SerialName("endTime") val endTime: String? = null, 11 | @SerialName("title") val title: String = "", 12 | @SerialName("target") val target: String? = null, 13 | @SerialName("writeAffiliation") val writeAffiliation: String = "", 14 | @SerialName("contentSummary") val contentSummary: String = "", 15 | @SerialName("likeCount") val likeCount: Int = 0, 16 | @SerialName("viewCount") val viewCount: Int = 0, 17 | @SerialName("category") val category: String = "", 18 | @SerialName("createdAt") val createdAt: String = "", 19 | @SerialName("logoImage") val logoImage: String? = null, 20 | @SerialName("saveCheck") val saveCheck: Boolean = false 21 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseNoticeQuickScanDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseNoticeQuickScanDto( 8 | @SerialName("universityName") 9 | val universityName: String = "", 10 | @SerialName("universityNameCount") 11 | val universityNameCount: Int = 0, 12 | @SerialName("universityLogoImage") 13 | val universityLogoImage: String = "", 14 | @SerialName("collegeDepartmentName") 15 | val collegeDepartmentName: String = "", 16 | @SerialName("collegeDepartmentCount") 17 | val collegeDepartmentCount: Int = 0, 18 | @SerialName("collegeDepartmentLogoImage") 19 | val collegeDepartmentLogoImage: String = "", 20 | @SerialName("departmentName") 21 | val departmentName: String = "", 22 | @SerialName("departmentCount") 23 | val departmentCount: Int = 0, 24 | @SerialName("departmentLogoImage") 25 | val departmentLogoImage: String = "", 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticeDetail/NoticeDetailAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticeDetail 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.ListAdapter 5 | import com.univoice.core_ui.view.ItemDiffCallback 6 | 7 | class NoticeDetailAdapter : 8 | ListAdapter( 9 | NoticeDetailItemDiffCallback 10 | ) { 11 | 12 | override fun onCreateViewHolder( 13 | parent: ViewGroup, 14 | viewType: Int 15 | ): NoticeDetailViewHolder { 16 | return NoticeDetailViewHolder.from(parent) 17 | } 18 | 19 | override fun onBindViewHolder( 20 | holder: NoticeDetailViewHolder, 21 | position: Int 22 | ) { 23 | val item = getItem(position) 24 | holder.bind(item) 25 | } 26 | 27 | companion object { 28 | private val NoticeDetailItemDiffCallback = 29 | ItemDiffCallback(onItemsTheSame = { old, new -> old == new }, 30 | onContentsTheSame = { old, new -> old == new }) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_post_date.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_bottom_button_create.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/repositoryimpl/QuickScanRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.repositoryimpl 2 | 3 | import com.univoice.data.datasource.QuickScanDataSource 4 | import com.univoice.data.dto.request.RequestQuickScanDto 5 | import com.univoice.data.mapper.toQuickScanListEntity 6 | import com.univoice.domain.entity.QuickScanListEntity 7 | import com.univoice.domain.repository.QuickScanRepository 8 | import javax.inject.Inject 9 | 10 | class QuickScanRepositoryImpl @Inject constructor( 11 | private val quickScanDataSource: QuickScanDataSource 12 | ) : QuickScanRepository { 13 | override suspend fun postQuickScan(writeAffiliation: String): Result?> { 14 | return runCatching { 15 | quickScanDataSource.postQuickScan(RequestQuickScanDto(writeAffiliation)).data?.map { it.toQuickScanListEntity() } 16 | } 17 | } 18 | 19 | override suspend fun postQuickScanViewCheck(noticeId: Int): Result { 20 | return runCatching { 21 | quickScanDataSource.postQuickScanViewCheck(noticeId) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeQuickscanItemDecorator.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.core_ui.util.context.pxToDp 8 | 9 | class HomeQuickscanItemDecorator(val context: Context) : RecyclerView.ItemDecoration() { 10 | override fun getItemOffsets( 11 | outRect: Rect, 12 | view: View, 13 | parent: RecyclerView, 14 | state: RecyclerView.State, 15 | ) { 16 | super.getItemOffsets(outRect, view, parent, state) 17 | val position = parent.getChildAdapterPosition(view) 18 | val itemCount = parent.adapter?.itemCount ?: 0 19 | 20 | when (position) { 21 | 0 -> { 22 | outRect.left = context.pxToDp(16) 23 | outRect.right = context.pxToDp(10) 24 | } 25 | 26 | itemCount - 1 -> outRect.right = context.pxToDp(16) 27 | 28 | else -> outRect.right = context.pxToDp(10) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeNoticeCategoryItemDecorator.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.core_ui.util.context.pxToDp 8 | 9 | class HomeNoticeCategoryItemDecorator(val context: Context) : RecyclerView.ItemDecoration() { 10 | override fun getItemOffsets( 11 | outRect: Rect, 12 | view: View, 13 | parent: RecyclerView, 14 | state: RecyclerView.State, 15 | ) { 16 | super.getItemOffsets(outRect, view, parent, state) 17 | val position = parent.getChildAdapterPosition(view) 18 | val itemCount = parent.adapter?.itemCount ?: 0 19 | 20 | when (position) { 21 | 0 -> { 22 | outRect.left = context.pxToDp(16) 23 | outRect.right = context.pxToDp(8) 24 | } 25 | 26 | itemCount - 1 -> outRect.right = context.pxToDp(16) 27 | 28 | else -> outRect.right = context.pxToDp(8) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeNoticeContentItemDecorator.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.core_ui.util.context.pxToDp 8 | 9 | class HomeNoticeContentItemDecorator(val context: Context) : RecyclerView.ItemDecoration() { 10 | override fun getItemOffsets( 11 | outRect: Rect, 12 | view: View, 13 | parent: RecyclerView, 14 | state: RecyclerView.State, 15 | ) { 16 | super.getItemOffsets(outRect, view, parent, state) 17 | val position = parent.getChildAdapterPosition(view) 18 | val itemCount = parent.adapter?.itemCount ?: 0 19 | 20 | when (position) { 21 | 0 -> { 22 | outRect.top = context.pxToDp(16) 23 | outRect.bottom = context.pxToDp(12) 24 | } 25 | 26 | itemCount - 1 -> outRect.bottom = context.pxToDp(16) 27 | 28 | else -> outRect.bottom = context.pxToDp(12) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/NoticePostImageItemDecorator.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.core_ui.util.context.pxToDp 8 | 9 | class NoticePostImageItemDecorator(val context: Context) : RecyclerView.ItemDecoration() { 10 | override fun getItemOffsets( 11 | outRect: Rect, 12 | view: View, 13 | parent: RecyclerView, 14 | state: RecyclerView.State, 15 | ) { 16 | super.getItemOffsets(outRect, view, parent, state) 17 | val position = parent.getChildAdapterPosition(view) 18 | val itemCount = parent.adapter?.itemCount ?: 0 19 | 20 | when (position) { 21 | 0 -> { 22 | outRect.left = context.pxToDp(16) 23 | outRect.right = context.pxToDp(12) 24 | } 25 | 26 | itemCount - 1 -> outRect.right = context.pxToDp(16) 27 | 28 | else -> outRect.right = context.pxToDp(12) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_home_notice_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_detail_like.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/util/BiggerDotPasswordTransformationMethod.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.util 2 | 3 | import android.content.Context 4 | import android.text.method.PasswordTransformationMethod 5 | import android.view.View 6 | import com.univoice.R 7 | 8 | class BiggerDotPasswordTransformationMethod(private val context: Context) : 9 | PasswordTransformationMethod() { 10 | override fun getTransformation(source: CharSequence?, view: View?): CharSequence { 11 | return BiggerDotCharSequence(super.getTransformation(source, view), context) 12 | } 13 | 14 | private class BiggerDotCharSequence( 15 | private val original: CharSequence, 16 | private val context: Context 17 | ) : CharSequence { 18 | override val length: Int 19 | get() = original.length 20 | 21 | override fun get(index: Int): Char { 22 | return context.getString(R.string.login_pwd_mask).single() 23 | } 24 | 25 | override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { 26 | return BiggerDotCharSequence(original.subSequence(startIndex, endIndex), context) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/QuickScanApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.request.RequestQuickScanDto 5 | import com.univoice.data.dto.response.ResponseQuickScanDto 6 | import com.univoice.data.dto.response.ResponseStorageDto 7 | import com.univoice.data_remote.api.ApiKeyStorage.API 8 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE 9 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE_ID 10 | import com.univoice.data_remote.api.ApiKeyStorage.QUICK 11 | import com.univoice.data_remote.api.ApiKeyStorage.V1 12 | import com.univoice.data_remote.api.ApiKeyStorage.VIEW_CHECK 13 | import retrofit2.http.Body 14 | import retrofit2.http.POST 15 | import retrofit2.http.Path 16 | 17 | interface QuickScanApiService { 18 | @POST("$API/$V1/$NOTICE/$QUICK") 19 | suspend fun postQuickScan( 20 | @Body requestQuickScanDto: RequestQuickScanDto 21 | ): BaseResponse> 22 | 23 | @POST("$API/$V1/$NOTICE/$VIEW_CHECK/{$NOTICE_ID}") 24 | suspend fun postQuickScanViewCheck( 25 | @Path(NOTICE_ID) noticeId: Int 26 | ): BaseResponse 27 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_notice_detail_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticeDetail/NoticeDetailViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticeDetail 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import coil.load 8 | import com.univoice.databinding.ItemNoticeDetailImageBinding 9 | 10 | class NoticeDetailViewHolder(private val binding: ItemNoticeDetailImageBinding) : 11 | RecyclerView.ViewHolder(binding.root) { 12 | 13 | fun bind(image: String) { 14 | with(binding) { 15 | if (image.isEmpty()) { 16 | ivNoticeDetailContent.visibility = View.GONE 17 | } else { 18 | ivNoticeDetailContent.visibility = View.VISIBLE 19 | ivNoticeDetailContent.load(image) 20 | } 21 | } 22 | } 23 | 24 | companion object { 25 | fun from(parent: ViewGroup): NoticeDetailViewHolder { 26 | val binding = ItemNoticeDetailImageBinding.inflate( 27 | LayoutInflater.from(parent.context), 28 | parent, 29 | false 30 | ) 31 | return NoticeDetailViewHolder(binding) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_post_photo.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/HomeDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.HomeDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.response.ResponseNoticeAllDto 6 | import com.univoice.data.dto.response.ResponseNoticeQuickScanDto 7 | import com.univoice.data_remote.api.HomeApiService 8 | import javax.inject.Inject 9 | 10 | class HomeDataSourceImpl @Inject constructor( 11 | private val homeApiService: HomeApiService 12 | ) : HomeDataSource { 13 | override suspend fun getNoticeQuickScan(): BaseResponse = 14 | homeApiService.getNoticeQuickScan() 15 | 16 | override suspend fun getNoticeAll(): BaseResponse> = 17 | homeApiService.getNoticeAll() 18 | 19 | override suspend fun getNoticeUniversity(): BaseResponse> = 20 | homeApiService.getNoticeUniversity() 21 | 22 | override suspend fun getNoticeCollege(): BaseResponse> = 23 | homeApiService.getNoticeCollege() 24 | 25 | override suspend fun getNoticeDepartment(): BaseResponse> = 26 | homeApiService.getNoticeDepartment() 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/storage/StorageViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.storage 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import coil.load 5 | import coil.transform.RoundedCornersTransformation 6 | import com.univoice.databinding.ItemNoticeBinding 7 | import com.univoice.domain.entity.NoticeListEntity 8 | import com.univoice.domain.entity.StorageListEntity 9 | import com.univoice.feature.util.CalculateDate 10 | 11 | class StorageViewHolder( 12 | private val binding: ItemNoticeBinding, 13 | private val onClick: (StorageListEntity) -> Unit = { _ -> }, 14 | ) : RecyclerView.ViewHolder(binding.root) { 15 | fun bind(data: StorageListEntity) { 16 | with(binding) { 17 | btnItemNoticeHeader.text = data.category 18 | tvItemNoticeTitle.text = data.title 19 | tvItemNoticeDate.text = CalculateDate().getCalculateDate(data.createdAt) 20 | tvItemNoticeLike.text = data.noticeLike.toString() 21 | tvItemNoticeViews.text = data.viewCount.toString() 22 | ivItemNoticeThumbnail.load(data.image) { 23 | transformations(RoundedCornersTransformation(5f)) 24 | } 25 | root.setOnClickListener { 26 | onClick(data) 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data/dto/response/ResponseNoticeDetailDto.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data.dto.response 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ResponseNoticeDetailDto( 8 | @SerialName("id") val id: Int, 9 | @SerialName("title") val title: String, 10 | @SerialName("content") val content: String, 11 | @SerialName("noticeLike") val noticeLike: Int, 12 | @SerialName("viewCount") val viewCount: Int, 13 | @SerialName("target") val target: String? = "", 14 | @SerialName("startTime") 15 | val startTime: String? = null, 16 | @SerialName("endTime") 17 | val endTime: String? = null, 18 | @SerialName("category") 19 | val category: String, 20 | @SerialName("contentSummary") 21 | val contentSummary: String, 22 | @SerialName("memberId") 23 | val memberId: Int, 24 | @SerialName("writeAffiliation") 25 | val writeAffiliation: String, 26 | @SerialName("noticeImages") 27 | val noticeImages: List, 28 | @SerialName("createdAt") 29 | val createdAt: String, 30 | @SerialName("likeCheck") 31 | val likeCheck: Boolean, 32 | @SerialName("saveCheck") 33 | val saveCheck: Boolean, 34 | @SerialName("dayOfWeek") 35 | val dayOfWeek: String 36 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeQuickscanViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import coil.load 6 | import com.univoice.databinding.ItemHomeNoticeQuickscanBinding 7 | import com.univoice.domain.entity.HomeQuickScanListEntity 8 | 9 | class HomeQuickscanViewHolder( 10 | private val binding: ItemHomeNoticeQuickscanBinding, 11 | private val click: (HomeQuickScanListEntity, Int) -> Unit = { _, _ -> }, 12 | ) : RecyclerView.ViewHolder(binding.root) { 13 | fun bind(data: HomeQuickScanListEntity) { 14 | with(binding) { 15 | if (data.count < 1) { 16 | layoutHomeNoticeQuickscanCount.visibility = View.INVISIBLE 17 | tvHomeNoticeQuickscanCount.visibility = View.INVISIBLE 18 | } else { 19 | tvHomeNoticeQuickscanCount.visibility = View.VISIBLE 20 | tvHomeNoticeQuickscanCount.text = data.count.toString() 21 | } 22 | 23 | tvHomeNoticeQuickscanName.text = data.name.replace(" ", "\n") 24 | ivHomeNoticeQuickscanProfile.load(data.image) 25 | 26 | root.setOnClickListener { 27 | click(data, bindingAdapterPosition) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_like.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/CreateAccountViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.data.dto.request.RequestCheckEmailDto 7 | import com.univoice.domain.repository.SignUpRepository 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableSharedFlow 10 | import kotlinx.coroutines.flow.SharedFlow 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class CreateAccountViewModel @Inject constructor( 16 | private val signUpRepository: SignUpRepository 17 | ) : ViewModel() { 18 | 19 | private val _emailCheckState = MutableSharedFlow>(replay = 0) 20 | val emailCheckState: SharedFlow> = _emailCheckState 21 | 22 | fun checkEmail(email: String) { 23 | viewModelScope.launch { 24 | _emailCheckState.emit(UiState.Loading) 25 | signUpRepository.postEmail(RequestCheckEmailDto(email)).onSuccess { 26 | _emailCheckState.emit(UiState.Success(it)) 27 | }.onFailure { 28 | _emailCheckState.emit(UiState.Failure(it.message ?: "")) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/storage/StorageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.storage 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.ListAdapter 6 | import com.univoice.core_ui.view.ItemDiffCallback 7 | import com.univoice.databinding.ItemNoticeBinding 8 | import com.univoice.domain.entity.NoticeListEntity 9 | import com.univoice.domain.entity.StorageListEntity 10 | 11 | class StorageAdapter( 12 | private val onClick: (StorageListEntity) -> Unit = { _ -> }, 13 | ) : ListAdapter( 14 | StorageAdapterDiffCallback 15 | ) { 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StorageViewHolder { 17 | val binding = ItemNoticeBinding.inflate(LayoutInflater.from(parent.context), parent, false) 18 | return StorageViewHolder(binding, onClick) 19 | } 20 | 21 | override fun onBindViewHolder(holder: StorageViewHolder, position: Int) { 22 | holder.bind(getItem(position)) 23 | } 24 | 25 | companion object { 26 | private val StorageAdapterDiffCallback = 27 | ItemDiffCallback( 28 | onItemsTheSame = { old, new -> old.id == new.id }, 29 | onContentsTheSame = { old, new -> old == new }) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/quickscan/QuickScanAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.quickscan 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.ListAdapter 6 | import com.univoice.core_ui.view.ItemDiffCallback 7 | import com.univoice.databinding.ItemQuickScanBinding 8 | import com.univoice.domain.entity.QuickScanListEntity 9 | 10 | class QuickScanAdapter( 11 | private val image: String, 12 | private val onClick: (Int, Boolean) -> Unit = { _, _ -> }, 13 | ) : ListAdapter( 14 | QuickScanDiffCallback 15 | ) { 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuickScanViewHolder { 17 | val binding = 18 | ItemQuickScanBinding.inflate(LayoutInflater.from(parent.context), parent, false) 19 | return QuickScanViewHolder(image, binding, onClick) 20 | } 21 | 22 | override fun onBindViewHolder(holder: QuickScanViewHolder, position: Int) { 23 | holder.bind(getItem(position)) 24 | } 25 | 26 | companion object { 27 | private val QuickScanDiffCallback = 28 | ItemDiffCallback( 29 | onItemsTheSame = { old, new -> old.id == new.id }, 30 | onContentsTheSame = { old, new -> old == new }) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/SchoolInputViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.domain.repository.SignUpRepository 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.flow.MutableStateFlow 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class SchoolInputViewModel @Inject constructor( 15 | private val signupRepository: SignUpRepository 16 | ) : ViewModel() { 17 | 18 | private val _schoolListState = MutableStateFlow>>(UiState.Empty) 19 | val schoolListState: StateFlow>> = _schoolListState 20 | 21 | init { 22 | postUniversityNames() 23 | } 24 | 25 | private fun postUniversityNames() { 26 | _schoolListState.value = UiState.Loading 27 | viewModelScope.launch { 28 | signupRepository.postUniversityNames().onSuccess { response -> 29 | _schoolListState.value = UiState.Success(response) 30 | }.onFailure { 31 | _schoolListState.value = UiState.Failure(it.message ?: "") 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/base/BindingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_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.annotation.LayoutRes 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.databinding.ViewDataBinding 10 | import androidx.fragment.app.Fragment 11 | 12 | abstract class BindingFragment( 13 | @LayoutRes private val layoutRes: Int 14 | ) : Fragment() { 15 | private var _binding: T? = null 16 | protected val binding 17 | get() = requireNotNull(_binding) { 18 | } 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) 26 | binding.lifecycleOwner = viewLifecycleOwner 27 | return binding.root 28 | } 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | initView() 33 | } 34 | 35 | protected abstract fun initView() 36 | 37 | override fun onDestroyView() { 38 | _binding = null 39 | super.onDestroyView() 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/HomeModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.HomeDataSource 4 | import com.univoice.data.repositoryimpl.HomeRepositoryImpl 5 | import com.univoice.data_remote.api.HomeApiService 6 | import com.univoice.data_remote.datasourceimpl.HomeDataSourceImpl 7 | import com.univoice.domain.repository.HomeRepository 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object HomeModule { 19 | @Provides 20 | @Singleton 21 | fun provideHomeService( 22 | @UniVoiceRetrofit retrofit: Retrofit, 23 | ): HomeApiService = retrofit.create(HomeApiService::class.java) 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | interface RepositoryModule { 28 | @Binds 29 | @Singleton 30 | fun bindsHomeRepository(RepositoryImpl: HomeRepositoryImpl): HomeRepository 31 | } 32 | 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | interface DataSourceModule { 36 | @Singleton 37 | @Binds 38 | fun providesHomeDataSource(DataSourceImpl: HomeDataSourceImpl): HomeDataSource 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/PostModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.PostDataSource 4 | import com.univoice.data.repositoryimpl.PostRepositoryImpl 5 | import com.univoice.data_remote.api.PostApiService 6 | import com.univoice.data_remote.datasourceimpl.PostDataSourceImpl 7 | import com.univoice.domain.repository.PostRepository 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object PostModule { 19 | @Provides 20 | @Singleton 21 | fun providePostService( 22 | @UniVoiceRetrofit retrofit: Retrofit, 23 | ): PostApiService = retrofit.create(PostApiService::class.java) 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | interface RepositoryModule { 28 | @Binds 29 | @Singleton 30 | fun bindsPostRepository(repositoryImpl: PostRepositoryImpl): PostRepository 31 | } 32 | 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | interface DataSourceModule { 36 | @Binds 37 | @Singleton 38 | fun providesPostDataSource(dataSourceImpl: PostDataSourceImpl): PostDataSource 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/base/BindingBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_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.annotation.LayoutRes 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.databinding.ViewDataBinding 10 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 11 | 12 | abstract class BindingBottomSheetFragment( 13 | @LayoutRes private val layoutRes: Int, 14 | ) : BottomSheetDialogFragment() { 15 | private var _binding: T? = null 16 | protected val binding get() = requireNotNull(_binding) 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, 20 | container: ViewGroup?, 21 | savedInstanceState: Bundle?, 22 | ): View? { 23 | _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) 24 | binding.lifecycleOwner = viewLifecycleOwner 25 | return binding.root 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | initView() 31 | } 32 | 33 | protected abstract fun initView() 34 | 35 | override fun onDestroyView() { 36 | _binding = null 37 | super.onDestroyView() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notice_detail_dislike.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/SettingModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.SettingDataSource 4 | import com.univoice.data.repositoryimpl.SettingRepositoryImpl 5 | import com.univoice.data_remote.api.SettingApiService 6 | import com.univoice.data_remote.datasourceimpl.SettingDataSourceImpl 7 | import com.univoice.domain.repository.SettingRepository 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object SettingModule { 19 | @Provides 20 | @Singleton 21 | fun provideSettingService( 22 | @UniVoiceRetrofit retrofit: Retrofit, 23 | ): SettingApiService = retrofit.create(SettingApiService::class.java) 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | interface RepositoryModule { 28 | @Binds 29 | @Singleton 30 | fun bindsSettingRepository(RepositoryImpl: SettingRepositoryImpl): SettingRepository 31 | } 32 | 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | interface DataSourceModule { 36 | @Singleton 37 | @Binds 38 | fun providesSettingDataSource(DataSourceImpl: SettingDataSourceImpl): SettingDataSource 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/SignUpModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.SignUpDataSource 4 | import com.univoice.data.repositoryimpl.SignUpRepositoryImpl 5 | import com.univoice.domain.repository.SignUpRepository 6 | import com.univoice.data_remote.api.SignUpApiService 7 | import com.univoice.data_remote.datasourceimpl.SignUpDataSourceImpl 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object SignUpModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideSignUpService( 23 | @UniVoiceRetrofit retrofit: Retrofit, 24 | ): SignUpApiService = retrofit.create(SignUpApiService::class.java) 25 | 26 | @Module 27 | @InstallIn(SingletonComponent::class) 28 | interface RepositoryModule { 29 | @Binds 30 | @Singleton 31 | fun bindsSignUpRepository(repositoryImpl: SignUpRepositoryImpl): SignUpRepository 32 | } 33 | 34 | @Module 35 | @InstallIn(SingletonComponent::class) 36 | interface DataSourceModule { 37 | @Binds 38 | @Singleton 39 | fun providesSignUpDataSource(dataSourceImpl: SignUpDataSourceImpl): SignUpDataSource 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/StorageModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.StorageDataSource 4 | import com.univoice.data.repositoryimpl.StorageRepositoryImpl 5 | import com.univoice.data_remote.api.StorageApiService 6 | import com.univoice.data_remote.datasourceimpl.StorageDataSourceImpl 7 | import com.univoice.domain.repository.StorageRepository 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object StorageModule { 19 | @Provides 20 | @Singleton 21 | fun provideStorageService( 22 | @UniVoiceRetrofit retrofit: Retrofit, 23 | ): StorageApiService = retrofit.create(StorageApiService::class.java) 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | interface RepositoryModule { 28 | @Binds 29 | @Singleton 30 | fun bindsStorageRepository(RepositoryImpl: StorageRepositoryImpl): StorageRepository 31 | } 32 | 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | interface DataSourceModule { 36 | @Singleton 37 | @Binds 38 | fun providesStorageDataSource(DataSourceImpl: StorageDataSourceImpl): StorageDataSource 39 | } 40 | } -------------------------------------------------------------------------------- /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. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/HomeApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseNoticeAllDto 5 | import com.univoice.data.dto.response.ResponseNoticeQuickScanDto 6 | import com.univoice.data_remote.api.ApiKeyStorage.ALL 7 | import com.univoice.data_remote.api.ApiKeyStorage.API 8 | import com.univoice.data_remote.api.ApiKeyStorage.COLLEGE_DEPARTMENT 9 | import com.univoice.data_remote.api.ApiKeyStorage.DEPARTMENT 10 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE 11 | import com.univoice.data_remote.api.ApiKeyStorage.UNIVERSITY 12 | import com.univoice.data_remote.api.ApiKeyStorage.V1 13 | import retrofit2.http.GET 14 | 15 | interface HomeApiService { 16 | @GET("/$API/$V1/$NOTICE/${ApiKeyStorage.QUICKHEAD}") 17 | suspend fun getNoticeQuickScan(): BaseResponse 18 | 19 | @GET("/$API/$V1/$NOTICE/$ALL") 20 | suspend fun getNoticeAll(): BaseResponse> 21 | 22 | @GET("/$API/$V1/$NOTICE/$UNIVERSITY") 23 | suspend fun getNoticeUniversity(): BaseResponse> 24 | 25 | @GET("/$API/$V1/$NOTICE/$COLLEGE_DEPARTMENT") 26 | suspend fun getNoticeCollege(): BaseResponse> 27 | 28 | @GET("/$API/$V1/$NOTICE/$DEPARTMENT") 29 | suspend fun getNoticeDepartment(): BaseResponse> 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/QuickScanModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.QuickScanDataSource 4 | import com.univoice.data.repositoryimpl.QuickScanRepositoryImpl 5 | import com.univoice.data_remote.api.QuickScanApiService 6 | import com.univoice.data_remote.datasourceimpl.QuickScanDataSourceImpl 7 | import com.univoice.domain.repository.QuickScanRepository 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object QuickScanModule { 19 | @Provides 20 | @Singleton 21 | fun provideQuickScanService( 22 | @UniVoiceRetrofit retrofit: Retrofit, 23 | ): QuickScanApiService = retrofit.create(QuickScanApiService::class.java) 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | interface RepositoryModule { 28 | @Binds 29 | @Singleton 30 | fun bindsQuickScanRepository(RepositoryImpl: QuickScanRepositoryImpl): QuickScanRepository 31 | } 32 | 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | interface DataSourceModule { 36 | @Singleton 37 | @Binds 38 | fun providesQuickScanDataSource(DataSourceImpl: QuickScanDataSourceImpl): QuickScanDataSource 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/storage/StorageViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.storage 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.domain.entity.StorageListEntity 7 | import com.univoice.domain.repository.StorageRepository 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class StorageViewModel @Inject constructor( 16 | private val storageRepository: StorageRepository 17 | ) : ViewModel() { 18 | private val _getStorageState = MutableStateFlow>>(UiState.Empty) 19 | val getStorageState: StateFlow>> = _getStorageState 20 | 21 | init { 22 | getStorageList() 23 | } 24 | 25 | fun getStorageList() = viewModelScope.launch { 26 | _getStorageState.emit(UiState.Loading) 27 | storageRepository.getSaves().fold( 28 | { 29 | if (it != null) _getStorageState.emit(UiState.Success(it)) else _getStorageState.emit( 30 | UiState.Failure("400") 31 | ) 32 | }, 33 | { _getStorageState.emit(UiState.Failure(it.message.toString())) } 34 | ) 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 26 | 27 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/NoticeDetailModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.NoticeDetailDataSource 4 | import com.univoice.data.repositoryimpl.NoticeDetailRepositoryImpl 5 | import com.univoice.data_remote.api.NoticeDetailApiService 6 | import com.univoice.data_remote.datasourceimpl.NoticeDetailDataSourceImpl 7 | import com.univoice.domain.repository.NoticeDetailRepository 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import retrofit2.Retrofit 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object NoticeDetailModule { 19 | @Provides 20 | @Singleton 21 | fun noticeDetailService( 22 | @UniVoiceRetrofit retrofit: Retrofit, 23 | ): NoticeDetailApiService = retrofit.create(NoticeDetailApiService::class.java) 24 | 25 | @Module 26 | @InstallIn(SingletonComponent::class) 27 | interface RepositoryModule { 28 | @Binds 29 | @Singleton 30 | fun bindsNoticeDetailRepository(RepositoryImpl: NoticeDetailRepositoryImpl): NoticeDetailRepository 31 | } 32 | 33 | @Module 34 | @InstallIn(SingletonComponent::class) 35 | interface DataSourceModule { 36 | @Singleton 37 | @Binds 38 | fun providesNoticeDetailDataSource(DataSourceImpl: NoticeDetailDataSourceImpl): NoticeDetailDataSource 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/NoticePostImageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost 2 | 3 | import android.net.Uri 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.univoice.databinding.ItemNoticePostImageBinding 9 | 10 | class NoticePostImageAdapter(private val imageUris: MutableList) : 11 | RecyclerView.Adapter() { 12 | 13 | inner class ViewHolder(binding: ItemNoticePostImageBinding) : 14 | RecyclerView.ViewHolder(binding.root) { 15 | val imageView: ImageView = binding.ivNoticePostImageItem 16 | val cancelImageView: ImageView = binding.ivPostingCancelImage 17 | } 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 20 | val binding = 21 | ItemNoticePostImageBinding.inflate(LayoutInflater.from(parent.context), parent, false) 22 | return ViewHolder(binding) 23 | } 24 | 25 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 26 | holder.imageView.setImageURI(imageUris[position]) 27 | holder.cancelImageView.setOnClickListener { 28 | imageUris.removeAt(position) 29 | notifyItemRemoved(position) 30 | notifyItemRangeChanged(position, imageUris.size) 31 | } 32 | } 33 | 34 | override fun getItemCount() = imageUris.size 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/DepartmentInputViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.data.dto.request.RequestDepartmentDto 7 | import com.univoice.domain.repository.SignUpRepository 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.launch 12 | import timber.log.Timber 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class DepartmentInputViewModel @Inject constructor( 17 | private val signupRepository: SignUpRepository 18 | ) : ViewModel() { 19 | 20 | private val _departmentListState = MutableStateFlow>>(UiState.Empty) 21 | val departmentListState: StateFlow>> = _departmentListState 22 | 23 | fun postDepartments(universityName: String) { 24 | _departmentListState.value = UiState.Loading 25 | viewModelScope.launch { 26 | val request = RequestDepartmentDto(universityName) 27 | signupRepository.postDepartments(request).onSuccess { response -> 28 | _departmentListState.value = UiState.Success(response.data ?: emptyList()) 29 | }.onFailure { 30 | _departmentListState.value = UiState.Failure(it.message ?: "") 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/util/CalculateTime.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.util 2 | 3 | import android.content.Context 4 | import com.univoice.R 5 | import java.time.LocalDateTime 6 | import java.time.format.DateTimeFormatter 7 | import java.time.temporal.ChronoUnit 8 | 9 | class CalculateTime(private val context: Context) { 10 | fun getCalculateTime(dateTimeString: String): String { 11 | val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") 12 | val targetDate = LocalDateTime.parse(dateTimeString, dateFormat) 13 | val currentDate = LocalDateTime.now() 14 | 15 | val minutesDifference = ChronoUnit.MINUTES.between(targetDate, currentDate) 16 | val hoursDifference = ChronoUnit.HOURS.between(targetDate, currentDate) 17 | val daysDifference = ChronoUnit.DAYS.between(targetDate, currentDate) 18 | 19 | return when { 20 | minutesDifference < TIME_THRESHOLD -> context.getString(R.string.feed_time_now) 21 | hoursDifference < TIME_THRESHOLD -> "$minutesDifference${context.getString(R.string.feed_time_minute)}" 22 | daysDifference < TIME_THRESHOLD -> "$hoursDifference${context.getString(R.string.feed_time_hour)}" 23 | else -> { 24 | val desiredFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd") 25 | return targetDate.format(desiredFormat) 26 | } 27 | } 28 | } 29 | 30 | companion object { 31 | private const val TIME_THRESHOLD = 1 32 | } 33 | 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/CheckInfoActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import android.content.Intent 4 | import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK 5 | import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 6 | import androidx.activity.OnBackPressedCallback 7 | import com.univoice.R 8 | import com.univoice.core_ui.base.BindingActivity 9 | import com.univoice.databinding.ActivityCheckInfoBinding 10 | import com.univoice.feature.entry.EntryActivity 11 | 12 | class CheckInfoActivity : 13 | BindingActivity(R.layout.activity_check_info) { 14 | 15 | override fun initView() { 16 | backPressedListener() 17 | initButtonClickListener() 18 | } 19 | 20 | private fun backPressedListener() { 21 | onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { 22 | override fun handleOnBackPressed() { 23 | Intent(this@CheckInfoActivity, EntryActivity::class.java).apply { 24 | flags = FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK 25 | startActivity(this) 26 | } 27 | } 28 | }) 29 | } 30 | 31 | private fun initButtonClickListener() { 32 | binding.btnCheckInfoStart.setOnClickListener { 33 | Intent(this, EntryActivity::class.java).apply { 34 | flags = FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK 35 | startActivity(this) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeNoticeContentAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.ListAdapter 6 | import com.univoice.core_ui.view.ItemDiffCallback 7 | import com.univoice.databinding.ItemHomeNoticeContentBinding 8 | import com.univoice.domain.entity.NoticeListEntity 9 | 10 | class HomeNoticeContentAdapter( 11 | private val click: (NoticeListEntity, Int) -> Unit = { _, _ -> }, 12 | ) : ListAdapter( 13 | HomeNoticeContentDiffCallback, 14 | ) { 15 | override fun onCreateViewHolder( 16 | parent: ViewGroup, 17 | viewType: Int, 18 | ): HomeNoticeContentViewHolder { 19 | val binding = 20 | ItemHomeNoticeContentBinding.inflate( 21 | LayoutInflater.from(parent.context), 22 | parent, 23 | false 24 | ) 25 | return HomeNoticeContentViewHolder(binding, click) 26 | } 27 | 28 | override fun onBindViewHolder( 29 | holder: HomeNoticeContentViewHolder, 30 | position: Int, 31 | ) { 32 | holder.bind(currentList[position]) 33 | } 34 | 35 | companion object { 36 | private val HomeNoticeContentDiffCallback = 37 | ItemDiffCallback( 38 | onItemsTheSame = { old, new -> old.id == new.id }, 39 | onContentsTheSame = { old, new -> old == new }, 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/login/LoginBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.login 2 | 3 | import android.app.Dialog 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.view.View 7 | import com.google.android.material.bottomsheet.BottomSheetBehavior 8 | import com.google.android.material.bottomsheet.BottomSheetDialog 9 | import com.univoice.R 10 | import com.univoice.core_ui.base.BindingBottomSheetFragment 11 | import com.univoice.databinding.FragmentLoginBottomSheetBinding 12 | import com.univoice.feature.signup.SignUpActivity 13 | 14 | class LoginBottomSheetFragment : 15 | BindingBottomSheetFragment(R.layout.fragment_login_bottom_sheet) { 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setStyle(STYLE_NORMAL, R.style.TransparentBottomSheetDialogFragment) 20 | } 21 | 22 | override fun initView() { 23 | initCloseBtnClickListener() 24 | initSignUpBtnClickListener() 25 | } 26 | 27 | private fun initSignUpBtnClickListener() { 28 | binding.btnLoginBottomSheetSignup.setOnClickListener { 29 | navigateToSignUp() 30 | } 31 | } 32 | 33 | private fun initCloseBtnClickListener() { 34 | binding.btnLoginBottomSheetClose.setOnClickListener { 35 | dismiss() 36 | } 37 | } 38 | 39 | private fun navigateToSignUp() { 40 | startActivity(Intent(requireContext(), SignUpActivity::class.java)) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/login/WelcomeActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.login 2 | 3 | import android.content.Intent 4 | import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK 5 | import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 6 | import androidx.activity.OnBackPressedCallback 7 | import com.univoice.R 8 | import com.univoice.core_ui.base.BindingActivity 9 | import com.univoice.databinding.ActivityWelcomeBinding 10 | import com.univoice.feature.MainActivity 11 | import com.univoice.feature.entry.EntryActivity 12 | 13 | class WelcomeActivity : BindingActivity(R.layout.activity_welcome) { 14 | override fun initView() { 15 | backPressedListener() 16 | initConfirmBtnClickListener() 17 | } 18 | 19 | private fun backPressedListener() { 20 | onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { 21 | override fun handleOnBackPressed() { 22 | Intent(this@WelcomeActivity, EntryActivity::class.java).apply { 23 | flags = FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK 24 | startActivity(this) 25 | } 26 | } 27 | }) 28 | } 29 | 30 | private fun initConfirmBtnClickListener() { 31 | binding.btnWelcomeConfirm.setOnClickListener { 32 | Intent(this, MainActivity::class.java).apply { 33 | flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK 34 | startActivity(this) 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_notice_post_image.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/setting/SettingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.setting 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.domain.entity.SettingUserEntity 7 | import com.univoice.domain.repository.SettingRepository 8 | import com.univoice.domain.repository.UserInfoRepository 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class SettingViewModel @Inject constructor( 17 | private val settingRepository: SettingRepository, 18 | private val userInfoRepository: UserInfoRepository 19 | ) : ViewModel() { 20 | private val _getMyPageState = MutableStateFlow>(UiState.Empty) 21 | val getMyPageState: StateFlow> = _getMyPageState 22 | 23 | init { 24 | getMyPage() 25 | } 26 | 27 | private fun getMyPage() = viewModelScope.launch { 28 | _getMyPageState.emit(UiState.Loading) 29 | settingRepository.getMyPage().fold( 30 | { 31 | _getMyPageState.emit(UiState.Success(it)) 32 | }, 33 | { _getMyPageState.emit(UiState.Failure(it.message.toString())) } 34 | ) 35 | } 36 | 37 | fun saveCheckLogin(checkLogin: Boolean) = 38 | viewModelScope.launch { userInfoRepository.saveCheckLogin(checkLogin) } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeQuickscanAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.ListAdapter 6 | import com.univoice.core_ui.view.ItemDiffCallback 7 | import com.univoice.databinding.ItemHomeNoticeQuickscanBinding 8 | import com.univoice.domain.entity.HomeQuickScanListEntity 9 | 10 | class HomeQuickscanAdapter( 11 | private val click: (HomeQuickScanListEntity, Int) -> Unit = { _, _ -> }, 12 | ) : 13 | ListAdapter( 14 | HomeQuickscanAdapterDiffCallback, 15 | ) { 16 | override fun onCreateViewHolder( 17 | parent: ViewGroup, 18 | viewType: Int, 19 | ): HomeQuickscanViewHolder { 20 | val binding = 21 | ItemHomeNoticeQuickscanBinding.inflate( 22 | LayoutInflater.from(parent.context), 23 | parent, 24 | false 25 | ) 26 | return HomeQuickscanViewHolder(binding, click) 27 | } 28 | 29 | override fun onBindViewHolder( 30 | holder: HomeQuickscanViewHolder, 31 | position: Int, 32 | ) { 33 | holder.bind(currentList[position]) 34 | } 35 | 36 | companion object { 37 | private val HomeQuickscanAdapterDiffCallback = 38 | ItemDiffCallback( 39 | onItemsTheSame = { old, new -> old.name == new.name }, 40 | onContentsTheSame = { old, new -> old == new }, 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/NoticeDetailDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.NoticeDetailDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.response.ResponseNoticeDetailDto 6 | import com.univoice.data_remote.api.NoticeDetailApiService 7 | import javax.inject.Inject 8 | 9 | class NoticeDetailDataSourceImpl @Inject constructor( 10 | private val noticeDetailApiService: NoticeDetailApiService 11 | ) : NoticeDetailDataSource { 12 | override suspend fun getNoticeDetail(noticeId: Int): BaseResponse { 13 | return noticeDetailApiService.getNoticeDetail(noticeId) 14 | } 15 | 16 | override suspend fun postNoticeLike(noticeId: Int): BaseResponse { 17 | return noticeDetailApiService.postNoticeLike(noticeId) 18 | } 19 | 20 | override suspend fun postNoticeCancelLike(noticeId: Int): BaseResponse { 21 | return noticeDetailApiService.postNoticeCancelLike(noticeId) 22 | } 23 | 24 | override suspend fun postNoticeDetailViewCount(noticeId: Int): BaseResponse { 25 | return noticeDetailApiService.postNoticeDetailViewCount(noticeId) 26 | } 27 | 28 | override suspend fun postNoticeDetailSave(noticeId: Int): BaseResponse { 29 | return noticeDetailApiService.postNoticeDetailSave(noticeId) 30 | } 31 | 32 | override suspend fun postNoticeDetailCancel(noticeId: Int): BaseResponse { 33 | return noticeDetailApiService.postNoticeDetailCancel(noticeId) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/home/HomeNoticeContentViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.home 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | import coil.load 6 | import coil.transform.RoundedCornersTransformation 7 | import com.univoice.databinding.ItemHomeNoticeContentBinding 8 | import com.univoice.domain.entity.NoticeListEntity 9 | import com.univoice.feature.util.CalculateDate 10 | 11 | class HomeNoticeContentViewHolder( 12 | private val binding: ItemHomeNoticeContentBinding, 13 | private val click: (NoticeListEntity, Int) -> Unit = { _, _ -> }, 14 | ) : RecyclerView.ViewHolder(binding.root) { 15 | fun bind(data: NoticeListEntity) { 16 | with(binding) { 17 | btnHomeNoticeContentCategory.text = data.category 18 | tvHomeNoticeContentTitle.text = data.title 19 | tvHomeNoticeContentLike.text = data.likeCount.toString() 20 | tvHomeNoticeContentView.text = data.viewCount.toString() 21 | tvHomeNoticeContentDate.text = CalculateDate().getCalculateDate(data.date) 22 | 23 | if (data.image.isEmpty()) { 24 | ivHomeNoticeContent.visibility = View.INVISIBLE 25 | } else { 26 | ivHomeNoticeContent.load(data.image) { 27 | ivHomeNoticeContent.visibility = View.VISIBLE 28 | transformations(RoundedCornersTransformation(5f)) 29 | } 30 | } 31 | 32 | root.setOnClickListener { 33 | click(data, bindingAdapterPosition) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | // Gray 6 | val Gray900 = Color(0xFF111111) 7 | val Gray800 = Color(0xFF333333) 8 | val Gray700 = Color(0xFF505050) 9 | val Gray600 = Color(0xFF666666) 10 | val Gray500 = Color(0xFF767676) 11 | val Gray400 = Color(0xFF888888) 12 | val Gray300 = Color(0xFF999999) 13 | val Gray200 = Color(0xFFBBBBBB) 14 | val Gray100 = Color(0xFFE1E1E1) 15 | val Gray50 = Color(0xFFF1F1F5) 16 | 17 | // Mint_main 18 | val Mint900 = Color(0xFF004F45) 19 | val Mint800 = Color(0xFF006559) 20 | val Mint700 = Color(0xFF008777) 21 | val Mint600 = Color(0xFF00A995) 22 | val Mint500 = Color(0xFF00B49E) 23 | val Mint400 = Color(0xFF00CBB2) 24 | val Mint300 = Color(0xFF00E1C6) 25 | val Mint200 = Color(0xFFB0F6ED) 26 | val Mint100 = Color(0xFFD9FBF6) 27 | val Mint50 = Color(0xFFE6FCF9) 28 | 29 | // Blue_sub 30 | val Blue900 = Color(0xFF102457) 31 | val Blue800 = Color(0xFF152F70) 32 | val Blue700 = Color(0xFF1C3E95) 33 | val Blue600 = Color(0xFF234EBB) 34 | val Blue500 = Color(0xFF2653C7) 35 | val Blue400 = Color(0xFF2A5EE0) 36 | val Blue300 = Color(0xFF2F68F9) 37 | val Blue200 = Color(0xFFBFD0FD) 38 | val Blue100 = Color(0xFFE0E8FE) 39 | val Blue50 = Color(0xFFEAF0FE) 40 | 41 | val White = Color(0xFFFFFFFF) 42 | 43 | // Font 44 | val Font_B01 = Color(0xFF111111) 45 | val Font_B02 = Color(0xFF555555) 46 | val Font_B03 = Color(0xFF767676) 47 | val Font_B04 = Color(0xFF999999) 48 | val Font_W01 = Color(0xFFFFFFFF) 49 | 50 | // Line 51 | val Light = Color(0xFFF1F1F5) 52 | val Regular = Color(0xFFE5E5EC) 53 | val Black = Color(0xFF111111) -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/DateDayAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | import com.univoice.feature.noticePost.timePicker.getDayFromCalendar 10 | import java.util.Calendar 11 | 12 | class DateDayAdapter( 13 | val days: ArrayList, 14 | private val dividerHeight: Int 15 | ) : 16 | RecyclerView.Adapter() { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 19 | val view = DateViewHolder( 20 | DataBindingUtil.inflate( 21 | LayoutInflater.from(parent.context), 22 | R.layout.item_date_number, 23 | parent, 24 | false 25 | ) 26 | ) 27 | view.binding.layoutDateNumber.layoutParams.height = 28 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 29 | return view 30 | } 31 | 32 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 33 | val viewHolder = holder as DateViewHolder 34 | viewHolder.binding.tvDateNumber.text = getDayFromCalendar(days[position]) 35 | } 36 | 37 | override fun getItemCount() = days.size 38 | 39 | override fun getItemId(position: Int): Long { 40 | return position.toLong() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/TimeDateAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | import com.univoice.feature.noticePost.timePicker.getDateFromCalendar 10 | import java.util.Calendar 11 | 12 | class TimeDateAdapter( 13 | val dates: ArrayList, 14 | private val dividerHeight: Int 15 | ) : 16 | RecyclerView.Adapter() { 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 18 | val view = DateViewHolder( 19 | DataBindingUtil.inflate( 20 | LayoutInflater.from(parent.context), 21 | R.layout.item_date_number, 22 | parent, 23 | false 24 | ) 25 | ) 26 | view.binding.layoutDateNumber.layoutParams.height = 27 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 28 | return view 29 | } 30 | 31 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 32 | val viewHolder = holder as DateViewHolder 33 | 34 | viewHolder.binding.tvDateNumber.text = getDateFromCalendar(dates[position]) 35 | } 36 | 37 | override fun getItemCount() = dates.size 38 | 39 | override fun getItemId(position: Int): Long { 40 | return position.toLong() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/DateMonthAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | import com.univoice.feature.noticePost.timePicker.getMonthFromCalendar 10 | import java.util.Calendar 11 | 12 | class DateMonthAdapter( 13 | val months: ArrayList, 14 | private val dividerHeight: Int 15 | ) : 16 | RecyclerView.Adapter() { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 19 | val view = DateViewHolder( 20 | DataBindingUtil.inflate( 21 | LayoutInflater.from(parent.context), 22 | R.layout.item_date_number, 23 | parent, 24 | false 25 | ) 26 | ) 27 | view.binding.layoutDateNumber.layoutParams.height = 28 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 29 | return view 30 | } 31 | 32 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 33 | val viewHolder = holder as DateViewHolder 34 | 35 | viewHolder.binding.tvDateNumber.text = getMonthFromCalendar(months[position]) 36 | } 37 | 38 | override fun getItemCount() = months.size 39 | 40 | override fun getItemId(position: Int): Long { 41 | return position.toLong() 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/DateYearAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | import com.univoice.feature.noticePost.timePicker.getYearFromCalendar 10 | import java.util.Calendar 11 | 12 | class DateYearAdapter( 13 | val years: ArrayList, 14 | private val dividerHeight: Int 15 | ) : 16 | RecyclerView.Adapter() { 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 19 | val view = DateViewHolder( 20 | DataBindingUtil.inflate( 21 | LayoutInflater.from(parent.context), 22 | R.layout.item_date_number, 23 | parent, 24 | false 25 | ) 26 | ) 27 | view.binding.layoutDateNumber.layoutParams.height = 28 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 29 | return view 30 | } 31 | 32 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 33 | val viewHolder = holder as DateViewHolder 34 | 35 | viewHolder.binding.tvDateNumber.text = getYearFromCalendar(years[position]) 36 | } 37 | 38 | 39 | override fun getItemCount() = years.size 40 | 41 | override fun getItemId(position: Int): Long { 42 | return position.toLong() 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/TimeMinuteAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | import com.univoice.feature.noticePost.timePicker.getMinuteFromCalendar 10 | import java.util.Calendar 11 | 12 | class TimeMinuteAdapter( 13 | val minute: ArrayList, 14 | private val dividerHeight: Int 15 | ) : 16 | RecyclerView.Adapter() { 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 18 | val view = DateViewHolder( 19 | DataBindingUtil.inflate( 20 | LayoutInflater.from(parent.context), 21 | R.layout.item_date_number, 22 | parent, 23 | false 24 | ) 25 | ) 26 | view.binding.layoutDateNumber.layoutParams.height = 27 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 28 | return view 29 | } 30 | 31 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 32 | val viewHolder = holder as DateViewHolder 33 | 34 | viewHolder.binding.tvDateNumber.text = getMinuteFromCalendar(minute[position]) 35 | } 36 | 37 | override fun getItemCount() = minute.size 38 | 39 | override fun getItemId(position: Int): Long { 40 | return position.toLong() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/TimeHourAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | import com.univoice.feature.noticePost.timePicker.getHourFromCalendar 10 | import java.util.Calendar 11 | 12 | 13 | class TimeHourAdapter( 14 | val hour: ArrayList, 15 | private val dividerHeight: Int 16 | ) : 17 | RecyclerView.Adapter() { 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 20 | val view = DateViewHolder( 21 | DataBindingUtil.inflate( 22 | LayoutInflater.from(parent.context), 23 | R.layout.item_date_number, 24 | parent, 25 | false 26 | ) 27 | ) 28 | view.binding.layoutDateNumber.layoutParams.height = 29 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 30 | return view 31 | } 32 | 33 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 34 | val viewHolder = holder as DateViewHolder 35 | 36 | viewHolder.binding.tvDateNumber.text = getHourFromCalendar(hour[position]) 37 | } 38 | 39 | 40 | override fun getItemCount() = hour.size 41 | 42 | override fun getItemId(position: Int): Long { 43 | return position.toLong() 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/timePicker/adapter/TimeMeridiemAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost.timePicker.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.univoice.R 8 | import com.univoice.feature.noticePost.timePicker.dpToPx 9 | 10 | class TimeMeridiemAdapter( 11 | val meridiem: ArrayList, 12 | private val dividerHeight: Int 13 | ) : 14 | RecyclerView.Adapter() { 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 16 | val view = DateViewHolder( 17 | DataBindingUtil.inflate( 18 | LayoutInflater.from(parent.context), 19 | R.layout.item_date_number, 20 | parent, 21 | false 22 | ) 23 | ) 24 | view.binding.layoutDateNumber.layoutParams.height = 25 | dividerHeight.dpToPx(view.binding.tvDateNumber.context).toInt() 26 | return view 27 | } 28 | 29 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 30 | val viewHolder = holder as DateViewHolder 31 | 32 | if (meridiem[position].isNotEmpty()) 33 | viewHolder.binding.tvDateNumber.text = meridiem[position] 34 | else 35 | viewHolder.binding.tvDateNumber.text = "" 36 | } 37 | 38 | override fun getItemCount() = meridiem.size 39 | 40 | override fun getItemId(position: Int): Long { 41 | return position.toLong() 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/app/di/LoginModule.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.app.di 2 | 3 | import com.univoice.data.datasource.LoginDataSource 4 | import com.univoice.data.repositoryimpl.LoginRepositoryImpl 5 | import com.univoice.data.repositoryimpl.UserInfoRepositoryImpl 6 | import com.univoice.data_remote.api.LoginApiService 7 | import com.univoice.data_remote.datasourceimpl.LoginDataSourceImpl 8 | import com.univoice.domain.repository.LoginRepository 9 | import com.univoice.domain.repository.UserInfoRepository 10 | import dagger.Binds 11 | import dagger.Module 12 | import dagger.Provides 13 | import dagger.hilt.InstallIn 14 | import dagger.hilt.components.SingletonComponent 15 | import retrofit2.Retrofit 16 | import javax.inject.Singleton 17 | 18 | @Module 19 | @InstallIn(SingletonComponent::class) 20 | object LoginModule { 21 | @Provides 22 | @Singleton 23 | fun provideLoginService( 24 | @UniVoiceRetrofit retrofit: Retrofit, 25 | ): LoginApiService = retrofit.create(LoginApiService::class.java) 26 | 27 | @Module 28 | @InstallIn(SingletonComponent::class) 29 | interface RepositoryModule { 30 | @Binds 31 | @Singleton 32 | fun bindsLoginRepository(RepositoryImpl: LoginRepositoryImpl): LoginRepository 33 | 34 | @Binds 35 | @Singleton 36 | fun bindsUserInfoRepository(RepositoryImpl: UserInfoRepositoryImpl): UserInfoRepository 37 | } 38 | 39 | @Module 40 | @InstallIn(SingletonComponent::class) 41 | interface DataSourceModule { 42 | @Singleton 43 | @Binds 44 | fun providesLoginDataSource(DataSourceImpl: LoginDataSourceImpl): LoginDataSource 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/noticePost/NoticePostViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.noticePost 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.domain.repository.PostRepository 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.flow.MutableSharedFlow 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.SharedFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.flow.asSharedFlow 13 | import kotlinx.coroutines.launch 14 | import java.io.File 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class NoticePostViewModel @Inject constructor( 19 | private val postRepository: PostRepository 20 | ) : ViewModel() { 21 | private val _postNoticeState = MutableSharedFlow>() 22 | val postNoticeState: SharedFlow> get() = _postNoticeState.asSharedFlow() 23 | 24 | fun postNotice( 25 | title: String, 26 | content: String, 27 | target: String?, 28 | startTime: String?, 29 | endTime: String?, 30 | noticeImages: List? 31 | ) = viewModelScope.launch { 32 | _postNoticeState.emit(UiState.Loading) 33 | postRepository.postSignUp( 34 | title, 35 | content, 36 | target, 37 | startTime, 38 | endTime, 39 | noticeImages, 40 | ).onSuccess { _postNoticeState.emit(UiState.Success(it)) } 41 | .onFailure { _postNoticeState.emit(UiState.Failure(it.message.toString())) } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/datasourceimpl/SignUpDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.datasourceimpl 2 | 3 | import com.univoice.data.datasource.SignUpDataSource 4 | import com.univoice.data.dto.BaseResponse 5 | import com.univoice.data.dto.request.RequestCheckEmailDto 6 | import com.univoice.data.dto.request.RequestDepartmentDto 7 | import com.univoice.data_remote.api.SignUpApiService 8 | import okhttp3.MultipartBody 9 | import okhttp3.RequestBody 10 | import javax.inject.Inject 11 | 12 | class SignUpDataSourceImpl @Inject constructor( 13 | private val signUpApiService: SignUpApiService 14 | ) : SignUpDataSource { 15 | override suspend fun postUniversityNames(): BaseResponse> = 16 | signUpApiService.postUniversityNames() 17 | 18 | override suspend fun postDepartments(requestDepartmentDto: RequestDepartmentDto): BaseResponse> = 19 | signUpApiService.postDepartments(requestDepartmentDto) 20 | 21 | override suspend fun postEmail(requestCheckEmailDto: RequestCheckEmailDto): BaseResponse = 22 | signUpApiService.postEmail(requestCheckEmailDto) 23 | 24 | override suspend fun postSignUp( 25 | admissionNumber: RequestBody, 26 | name: RequestBody, 27 | studentNumber: RequestBody, 28 | email: RequestBody, 29 | password: RequestBody, 30 | universityName: RequestBody, 31 | departmentName: RequestBody, 32 | studentCardImage: MultipartBody.Part 33 | ): BaseResponse { 34 | return signUpApiService.postSignUp(admissionNumber, name, studentNumber, email, password, universityName, departmentName, studentCardImage) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/base/BindingDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_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 android.view.WindowManager 8 | import androidx.annotation.LayoutRes 9 | import androidx.databinding.DataBindingUtil 10 | import androidx.databinding.ViewDataBinding 11 | import androidx.fragment.app.DialogFragment 12 | 13 | abstract class BindingDialogFragment( 14 | @LayoutRes val layoutRes: Int 15 | ) : DialogFragment() { 16 | private var _binding: T? = null 17 | protected val binding get() = requireNotNull(_binding) { { "binding object is not initialized" } } 18 | 19 | override fun onStart() { 20 | super.onStart() 21 | dialog?.window?.apply { 22 | setLayout( 23 | WindowManager.LayoutParams.WRAP_CONTENT, 24 | WindowManager.LayoutParams.WRAP_CONTENT 25 | ) 26 | } 27 | } 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, 31 | container: ViewGroup?, 32 | savedInstanceState: Bundle? 33 | ): View { 34 | _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) 35 | return binding.root 36 | } 37 | 38 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 39 | super.onViewCreated(view, savedInstanceState) 40 | binding.lifecycleOwner = viewLifecycleOwner 41 | initView() 42 | } 43 | 44 | protected abstract fun initView() 45 | 46 | override fun onDestroyView() { 47 | _binding = null 48 | super.onDestroyView() 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature 2 | 3 | import android.view.View 4 | import androidx.navigation.NavController 5 | import androidx.navigation.fragment.NavHostFragment 6 | import androidx.navigation.fragment.findNavController 7 | import androidx.navigation.ui.setupWithNavController 8 | import com.univoice.R 9 | import com.univoice.core_ui.base.BindingActivity 10 | import com.univoice.databinding.ActivityMainBinding 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | class MainActivity : BindingActivity(R.layout.activity_main) { 15 | override fun initView() { 16 | initMainBottomNavigation() 17 | } 18 | 19 | private fun initMainBottomNavigation() { 20 | val navController = (supportFragmentManager.findFragmentById(R.id.fcv_main_nav) as NavHostFragment).findNavController() 21 | binding.bnvMainNav.apply { 22 | setupWithNavController(navController) 23 | itemIconTintList = null 24 | } 25 | 26 | setBottomNavigationVisible(navController) 27 | } 28 | 29 | private fun setBottomNavigationVisible(navController: NavController) { 30 | navController.addOnDestinationChangedListener { _, destination, _ -> 31 | binding.bnvMainNav.visibility = 32 | if (destination.id in 33 | listOf( 34 | R.id.fragment_home, 35 | R.id.fragment_storage, 36 | R.id.fragment_setting 37 | ) 38 | ) { 39 | View.VISIBLE 40 | } else { 41 | View.GONE 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_storage.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 26 | 27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_local/UserPreferencesDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_local 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.datastore.preferences.core.booleanPreferencesKey 6 | import androidx.datastore.preferences.core.edit 7 | import androidx.datastore.preferences.core.stringPreferencesKey 8 | import com.univoice.data.datasource.UserPreferencesDataSource 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.map 11 | import javax.inject.Inject 12 | 13 | class UserPreferencesDataSourceImpl @Inject constructor 14 | ( 15 | private val dataStore: DataStore 16 | ) : UserPreferencesDataSource { 17 | private val USER_ACCESSTOKEN = stringPreferencesKey("user_accesstoken") 18 | private val CHECK_LOGIN = booleanPreferencesKey("check_login") 19 | 20 | override suspend fun saveUserAccessToken(accessToken: String) { 21 | dataStore.edit { preferences -> 22 | preferences[USER_ACCESSTOKEN] = accessToken 23 | } 24 | } 25 | 26 | override fun getUserAccessToken(): Flow = dataStore.data.map { preferences -> 27 | preferences[USER_ACCESSTOKEN] 28 | } 29 | 30 | override suspend fun saveCheckLogin(checkLogin: Boolean) { 31 | dataStore.edit { preferences -> 32 | preferences[CHECK_LOGIN] = checkLogin 33 | } 34 | } 35 | 36 | override fun getCheckLogin(): Flow = dataStore.data.map { preferences -> 37 | preferences[CHECK_LOGIN] ?: false 38 | } 39 | 40 | override suspend fun clear() { 41 | dataStore.edit { preferences -> 42 | preferences.remove(USER_ACCESSTOKEN) 43 | preferences.remove(CHECK_LOGIN) 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/core_ui/CustomSpinner.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.core_ui 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.Spinner 6 | import androidx.appcompat.widget.AppCompatSpinner 7 | 8 | class CustomSpinner @JvmOverloads constructor( 9 | context: Context, 10 | attrs: AttributeSet? = null, 11 | defStyleAttr: Int = 0 12 | ) : AppCompatSpinner(context, attrs, defStyleAttr) { 13 | 14 | interface OnSpinnerEventsListener { 15 | fun onSpinnerOpened(spinner: Spinner?) 16 | 17 | fun onSpinnerClosed(spinner: Spinner?) 18 | } 19 | 20 | private var mListener: OnSpinnerEventsListener? = null 21 | private var mOpenInitiated = false 22 | 23 | override fun performClick(): Boolean { 24 | mOpenInitiated = true 25 | mListener?.onSpinnerOpened(this) 26 | return super.performClick() 27 | } 28 | 29 | override fun onWindowFocusChanged(hasFocus: Boolean) { 30 | if (hasBeenOpened() && hasFocus) { 31 | performClosedEvent() 32 | } 33 | } 34 | 35 | /** 36 | * Register the listener which will listen for events. 37 | */ 38 | fun setSpinnerEventsListener( 39 | onSpinnerEventsListener: OnSpinnerEventsListener? 40 | ) { 41 | mListener = onSpinnerEventsListener 42 | } 43 | 44 | /** 45 | * Propagate the closed Spinner event to the listener from outside if needed. 46 | */ 47 | fun performClosedEvent() { 48 | mOpenInitiated = false 49 | mListener?.onSpinnerClosed(this) 50 | } 51 | 52 | /** 53 | * A boolean flag indicating that the Spinner triggered an open event. 54 | * 55 | * @return true for opened Spinner 56 | */ 57 | fun hasBeenOpened(): Boolean { 58 | return mOpenInitiated 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Handler 6 | import androidx.activity.viewModels 7 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 8 | import com.univoice.R 9 | import com.univoice.core_ui.base.BindingActivity 10 | import com.univoice.databinding.ActivitySplashBinding 11 | import com.univoice.feature.entry.EntryActivity 12 | import com.univoice.feature.login.LoginViewModel 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import kotlinx.coroutines.flow.first 15 | import kotlinx.coroutines.runBlocking 16 | 17 | @AndroidEntryPoint 18 | class SplashActivity : BindingActivity(R.layout.activity_splash) { 19 | private val loginViewModel by viewModels() 20 | 21 | override fun initView() { 22 | installSplashScreen() 23 | initSplash() 24 | } 25 | 26 | private fun initSplash() { 27 | Handler().postDelayed({ 28 | runBlocking { 29 | when { 30 | (loginViewModel.getUserAccessToken().toString().isNotBlank() && 31 | loginViewModel.getCheckLogin().first()) -> { 32 | navigateTo() 33 | } 34 | 35 | else -> { 36 | navigateTo() 37 | } 38 | } 39 | finish() 40 | } 41 | }, 3000) 42 | } 43 | 44 | private inline fun navigateTo() { 45 | Intent(this@SplashActivity, T::class.java).apply { 46 | addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) 47 | startActivity(this) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/signup/SignupBottomSheetFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.signup 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.domain.repository.SignUpRepository 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.flow.MutableSharedFlow 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.SharedFlow 11 | import kotlinx.coroutines.flow.StateFlow 12 | import kotlinx.coroutines.flow.asSharedFlow 13 | import kotlinx.coroutines.launch 14 | import java.io.File 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class SignupBottomSheetFragmentViewModel @Inject constructor( 19 | private val signUpRepository: SignUpRepository 20 | ) : ViewModel() { 21 | 22 | private val _postSignupState = MutableSharedFlow>() 23 | val postSignupState: SharedFlow> get() = _postSignupState.asSharedFlow() 24 | 25 | fun postSignUp( 26 | admissionNumber: String, 27 | name: String, 28 | studentNumber: String, 29 | email: String, 30 | password: String, 31 | universityName: String, 32 | departmentName: String, 33 | studentCardImage: File 34 | ) = viewModelScope.launch { 35 | _postSignupState.emit(UiState.Loading) 36 | signUpRepository.postSignUp( 37 | admissionNumber, 38 | name, 39 | studentNumber, 40 | email, 41 | password, 42 | universityName, 43 | departmentName, 44 | studentCardImage 45 | ).onSuccess { _postSignupState.emit(UiState.Success(it)) } 46 | .onFailure { _postSignupState.emit(UiState.Failure(it.message.toString())) } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/data_remote/api/NoticeDetailApiService.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.data_remote.api 2 | 3 | import com.univoice.data.dto.BaseResponse 4 | import com.univoice.data.dto.response.ResponseNoticeDetailDto 5 | import com.univoice.data_remote.api.ApiKeyStorage.API 6 | import com.univoice.data_remote.api.ApiKeyStorage.CANCEL 7 | import com.univoice.data_remote.api.ApiKeyStorage.LIKE 8 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE 9 | import com.univoice.data_remote.api.ApiKeyStorage.NOTICE_ID 10 | import com.univoice.data_remote.api.ApiKeyStorage.SAVE 11 | import com.univoice.data_remote.api.ApiKeyStorage.V1 12 | import com.univoice.data_remote.api.ApiKeyStorage.VIEW_COUNT 13 | import retrofit2.http.GET 14 | import retrofit2.http.POST 15 | import retrofit2.http.Path 16 | 17 | interface NoticeDetailApiService { 18 | @GET("/$API/$V1/$NOTICE/{$NOTICE_ID}") 19 | suspend fun getNoticeDetail( 20 | @Path(NOTICE_ID) noticeId: Int 21 | ): BaseResponse 22 | 23 | @POST("/$API/$V1/$NOTICE/$LIKE/{$NOTICE_ID}") 24 | suspend fun postNoticeLike( 25 | @Path(NOTICE_ID) noticeId: Int 26 | ): BaseResponse 27 | 28 | @POST("/$API/$V1/$NOTICE/$LIKE/$CANCEL/{$NOTICE_ID}") 29 | suspend fun postNoticeCancelLike( 30 | @Path(NOTICE_ID) noticeId: Int 31 | ): BaseResponse 32 | 33 | @POST("$API/$V1/$NOTICE/$VIEW_COUNT/{$NOTICE_ID}") 34 | suspend fun postNoticeDetailViewCount( 35 | @Path(NOTICE_ID) noticeId: Int 36 | ): BaseResponse 37 | 38 | @POST("$API/$V1/$NOTICE/$SAVE/{$NOTICE_ID}") 39 | suspend fun postNoticeDetailSave( 40 | @Path(NOTICE_ID) noticeId: Int 41 | ): BaseResponse 42 | 43 | @POST("$API/$V1/$NOTICE/$SAVE/$CANCEL/{$NOTICE_ID}") 44 | suspend fun postNoticeDetailCancel( 45 | @Path(NOTICE_ID) noticeId: Int 46 | ): BaseResponse 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/univoice/feature/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.univoice.feature.login 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.univoice.core_ui.view.UiState 6 | import com.univoice.domain.repository.LoginRepository 7 | import com.univoice.domain.repository.UserInfoRepository 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | import kotlinx.coroutines.flow.StateFlow 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class LoginViewModel @Inject constructor( 16 | private val loginRepository: LoginRepository, 17 | private val userInfoRepository: UserInfoRepository 18 | ) : ViewModel() { 19 | private val _postLoginState = MutableStateFlow>(UiState.Empty) 20 | val postLoginState: StateFlow> = _postLoginState 21 | 22 | fun postLogin(email: String, password: String) = viewModelScope.launch { 23 | _postLoginState.emit(UiState.Loading) 24 | loginRepository.postLogin(email, password).fold( 25 | { 26 | if (it != null) _postLoginState.emit(UiState.Success(it)) else _postLoginState.emit( 27 | UiState.Failure("400") 28 | ) 29 | }, 30 | { _postLoginState.emit(UiState.Failure(it.message.toString())) } 31 | ) 32 | } 33 | 34 | fun getUserAccessToken() = userInfoRepository.getUserAccessToken() 35 | 36 | fun saveUserAccessToken(accessToken: String) { 37 | viewModelScope.launch { 38 | userInfoRepository.saveUserAccessToken(accessToken) 39 | } 40 | } 41 | 42 | fun getCheckLogin() = userInfoRepository.getCheckLogin() 43 | 44 | fun saveCheckLogin(checkLogin: Boolean) { 45 | viewModelScope.launch { 46 | userInfoRepository.saveCheckLogin(checkLogin) 47 | } 48 | } 49 | } --------------------------------------------------------------------------------