├── app ├── .gitignore ├── release │ ├── app-release.apk │ └── output.json ├── src │ └── main │ │ ├── java │ │ └── app │ │ │ └── itgungnir │ │ │ └── kwa │ │ │ ├── App.kt │ │ │ └── SplashActivity.kt │ │ └── AndroidManifest.xml └── build.gradle ├── app_main ├── proguard-rules.pro ├── .gitignore ├── src │ └── main │ │ ├── java │ │ └── app │ │ │ └── itgungnir │ │ │ └── kwa │ │ │ └── main │ │ │ ├── mine │ │ │ ├── add │ │ │ │ ├── AddArticleState.kt │ │ │ │ ├── AddArticleViewModel.kt │ │ │ │ └── AddArticleDialog.kt │ │ │ ├── MineState.kt │ │ │ └── MineArticleDelegate.kt │ │ │ ├── project │ │ │ ├── ProjectState.kt │ │ │ ├── child │ │ │ │ ├── ProjectChildState.kt │ │ │ │ └── ProjectChildDelegate.kt │ │ │ ├── ProjectViewModel.kt │ │ │ └── ProjectFragment.kt │ │ │ ├── weixin │ │ │ ├── WeixinState.kt │ │ │ ├── child │ │ │ │ ├── WeixinChildState.kt │ │ │ │ └── WeixinChildDelegate.kt │ │ │ └── WeixinViewModel.kt │ │ │ ├── tree │ │ │ ├── tools │ │ │ │ ├── ToolsState.kt │ │ │ │ ├── ToolsViewModel.kt │ │ │ │ └── ToolsDialog.kt │ │ │ ├── TreeState.kt │ │ │ ├── navigation │ │ │ │ ├── NavigationState.kt │ │ │ │ ├── SideBarDelegate.kt │ │ │ │ ├── NavigationViewModel.kt │ │ │ │ └── NavigationDelegate.kt │ │ │ ├── TreeViewModel.kt │ │ │ └── TreeDelegate.kt │ │ │ ├── main │ │ │ ├── MainState.kt │ │ │ └── MainViewModel.kt │ │ │ └── home │ │ │ ├── search │ │ │ ├── SearchState.kt │ │ │ ├── SearchViewModel.kt │ │ │ ├── SearchHotKeyDelegate.kt │ │ │ ├── SearchDialog.kt │ │ │ └── SearchHistoryDelegate.kt │ │ │ ├── HomeState.kt │ │ │ └── delegate │ │ │ ├── BannerDelegate.kt │ │ │ └── HomeArticleDelegate.kt │ │ ├── res │ │ └── layout │ │ │ ├── fragment_weixin_child.xml │ │ │ ├── fragment_project_child.xml │ │ │ ├── list_item_home_banner_child.xml │ │ │ ├── list_item_tree_child.xml │ │ │ ├── list_item_navigation_left.xml │ │ │ ├── fragment_home.xml │ │ │ ├── fragment_mine.xml │ │ │ ├── fragment_tree.xml │ │ │ ├── list_item_tag.xml │ │ │ ├── list_item_navigation_right.xml │ │ │ ├── fragment_project.xml │ │ │ ├── list_item_search_hot.xml │ │ │ ├── list_item_main_bottom_tab.xml │ │ │ ├── dialog_search.xml │ │ │ ├── fragment_weixin.xml │ │ │ ├── activity_main.xml │ │ │ ├── dialog_add_article.xml │ │ │ ├── dialog_tools.xml │ │ │ ├── list_item_home_banner.xml │ │ │ ├── list_item_search_history.xml │ │ │ ├── dialog_navigation.xml │ │ │ ├── list_item_tree.xml │ │ │ ├── list_item_weixin_article.xml │ │ │ └── list_item_mine_article.xml │ │ └── AndroidManifest.xml └── build.gradle ├── common ├── .gitignore ├── src │ └── main │ │ ├── assets │ │ └── iconfont.ttf │ │ ├── res │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── img_placeholder.png │ │ │ └── ic_launcher_round.png │ │ ├── anim │ │ │ ├── dd_mask_in.xml │ │ │ ├── dd_mask_out.xml │ │ │ ├── dd_menu_in.xml │ │ │ └── dd_menu_out.xml │ │ ├── layout │ │ │ ├── view_status_flex.xml │ │ │ ├── view_status_web.xml │ │ │ ├── view_status_list.xml │ │ │ ├── view_status_flex_empty.xml │ │ │ ├── view_list_footer.xml │ │ │ ├── view_status_list_empty.xml │ │ │ └── view_status_error.xml │ │ ├── values │ │ │ ├── dimens.xml │ │ │ └── colors.xml │ │ └── values-night │ │ │ └── colors.xml │ │ ├── java │ │ └── app │ │ │ └── itgungnir │ │ │ └── kwa │ │ │ └── common │ │ │ ├── http │ │ │ ├── HttpException.kt │ │ │ ├── Result.kt │ │ │ ├── HttpExt.kt │ │ │ └── HttpUtil.kt │ │ │ ├── util │ │ │ ├── Util.kt │ │ │ ├── XGlideModule.kt │ │ │ ├── ReduxUtil.kt │ │ │ ├── DateTimeUtil.kt │ │ │ ├── CrashDetectUtil.kt │ │ │ ├── LeakDetectUtil.kt │ │ │ ├── ThemeUtil.kt │ │ │ ├── AppConfig.kt │ │ │ ├── LoggingUtil.kt │ │ │ ├── ScreenAdaptUtil.kt │ │ │ └── CacheUtil.kt │ │ │ ├── dto │ │ │ ├── VersionResponse.kt │ │ │ ├── NavigationResponse.kt │ │ │ ├── TagResponse.kt │ │ │ ├── ArticleListResponse.kt │ │ │ ├── BannerResponse.kt │ │ │ ├── ScheduleListResponse.kt │ │ │ ├── TabResponse.kt │ │ │ ├── LoginResponse.kt │ │ │ ├── ScheduleResponse.kt │ │ │ └── ArticleResponse.kt │ │ │ ├── redux │ │ │ ├── AppState.kt │ │ │ ├── Actions.kt │ │ │ ├── AppRedux.kt │ │ │ └── AppReducer.kt │ │ │ └── Constant.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── app_support ├── .gitignore ├── proguard-rules.pro ├── src │ └── main │ │ ├── java │ │ └── app │ │ │ └── itgungnir │ │ │ └── kwa │ │ │ └── support │ │ │ ├── web │ │ │ ├── WebState.kt │ │ │ └── WebViewModel.kt │ │ │ ├── register │ │ │ ├── RegisterState.kt │ │ │ ├── RegisterViewModel.kt │ │ │ └── RegisterActivity.kt │ │ │ ├── login │ │ │ ├── LoginState.kt │ │ │ └── LoginViewModel.kt │ │ │ ├── schedule │ │ │ ├── done │ │ │ │ └── ScheduleDoneState.kt │ │ │ ├── ScheduleState.kt │ │ │ └── menu │ │ │ │ ├── MenuView.kt │ │ │ │ ├── MenuItemDelegate.kt │ │ │ │ └── MenuTabBar.kt │ │ │ ├── hierarchy │ │ │ ├── HierarchyChildState.kt │ │ │ ├── HierarchyActivity.kt │ │ │ └── HierarchyChildDelegate.kt │ │ │ ├── search_result │ │ │ ├── SearchResultState.kt │ │ │ └── SearchResultDelegate.kt │ │ │ └── setting │ │ │ ├── delegate │ │ │ ├── DividerDelegate.kt │ │ │ ├── ButtonDelegate.kt │ │ │ ├── NavigableDelegate.kt │ │ │ ├── CheckableDelegate.kt │ │ │ └── DigitalDelegate.kt │ │ │ ├── SettingState.kt │ │ │ ├── AboutUsDialog.kt │ │ │ └── SettingViewModel.kt │ │ ├── res │ │ └── layout │ │ │ ├── list_item_setting_divider.xml │ │ │ ├── fragment_hierarchy_child.xml │ │ │ ├── view_schedule_menu_content.xml │ │ │ ├── list_item_setting_button.xml │ │ │ ├── view_schedule_menu.xml │ │ │ ├── activity_schedule.xml │ │ │ ├── activity_setting.xml │ │ │ ├── activity_schedule_done.xml │ │ │ ├── activity_search_result.xml │ │ │ ├── list_item_menu.xml │ │ │ ├── activity_hierarchy.xml │ │ │ ├── activity_web.xml │ │ │ ├── dialog_about_us.xml │ │ │ ├── list_item_setting_checkable.xml │ │ │ ├── activity_register.xml │ │ │ ├── list_item_setting_navigable.xml │ │ │ ├── list_item_setting_digital.xml │ │ │ ├── activity_login.xml │ │ │ ├── list_item_schedule.xml │ │ │ ├── list_item_hierarchy_article.xml │ │ │ └── list_item_search_article.xml │ │ └── AndroidManifest.xml └── build.gradle ├── ic_launcher_origin.png ├── settings.gradle ├── images ├── screen_shot_01.png ├── screen_shot_02.png ├── screen_shot_03.png ├── screen_shot_04.png ├── screen_shot_05.png ├── screen_shot_06.png ├── screen_shot_07.png ├── screen_shot_08.png ├── screen_shot_09.png ├── screen_shot_10.png ├── screen_shot_11.png ├── screen_shot_12.png ├── screen_shot_13.png ├── screen_shot_14.png ├── screen_shot_15.png ├── screen_shot_16.png ├── screen_shot_17.png ├── screen_shot_18.png ├── screen_shot_19.png ├── screen_shot_20.png ├── screen_shot_21.png └── project_structure.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── version.json ├── gradle.properties ├── config.gradle └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app_main/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app_main/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app_support/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app_support/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ic_launcher_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/ic_launcher_origin.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | include ':app_main', ':app_support' 4 | 5 | include ':common' 6 | -------------------------------------------------------------------------------- /images/screen_shot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_01.png -------------------------------------------------------------------------------- /images/screen_shot_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_02.png -------------------------------------------------------------------------------- /images/screen_shot_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_03.png -------------------------------------------------------------------------------- /images/screen_shot_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_04.png -------------------------------------------------------------------------------- /images/screen_shot_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_05.png -------------------------------------------------------------------------------- /images/screen_shot_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_06.png -------------------------------------------------------------------------------- /images/screen_shot_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_07.png -------------------------------------------------------------------------------- /images/screen_shot_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_08.png -------------------------------------------------------------------------------- /images/screen_shot_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_09.png -------------------------------------------------------------------------------- /images/screen_shot_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_10.png -------------------------------------------------------------------------------- /images/screen_shot_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_11.png -------------------------------------------------------------------------------- /images/screen_shot_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_12.png -------------------------------------------------------------------------------- /images/screen_shot_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_13.png -------------------------------------------------------------------------------- /images/screen_shot_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_14.png -------------------------------------------------------------------------------- /images/screen_shot_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_15.png -------------------------------------------------------------------------------- /images/screen_shot_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_16.png -------------------------------------------------------------------------------- /images/screen_shot_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_17.png -------------------------------------------------------------------------------- /images/screen_shot_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_18.png -------------------------------------------------------------------------------- /images/screen_shot_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_19.png -------------------------------------------------------------------------------- /images/screen_shot_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_20.png -------------------------------------------------------------------------------- /images/screen_shot_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/screen_shot_21.png -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/app/release/app-release.apk -------------------------------------------------------------------------------- /images/project_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/images/project_structure.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /common/src/main/assets/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/common/src/main/assets/iconfont.ttf -------------------------------------------------------------------------------- /common/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/common/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /common/src/main/res/mipmap-xxhdpi/img_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/common/src/main/res/mipmap-xxhdpi/img_placeholder.png -------------------------------------------------------------------------------- /common/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ITGungnir/KotlinWanAndroid/HEAD/common/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/http/HttpException.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.http 2 | 3 | class HttpException(errorMsg: String) : Throwable(message = errorMsg) -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/http/Result.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.http 2 | 3 | data class Result( 4 | val data: T, 5 | val errorCode: Int, 6 | val errorMsg: String 7 | ) -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/Util.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | 5 | interface Util { 6 | 7 | fun init(application: Application) 8 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/web/WebState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.web 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class WebState( 6 | val error: Throwable? = null 7 | ) : State -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/VersionResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class VersionResponse( 4 | val downloadUrl: String, 5 | val version: String, 6 | val versionDesc: String 7 | ) -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/NavigationResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class NavigationResponse( 4 | val articles: List, 5 | val cid: Int, 6 | val name: String 7 | ) -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":6,"versionName":"1.2.2","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/TagResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class TagResponse( 4 | val id: Int, 5 | val link: String, 6 | val name: String, 7 | val order: Int, 8 | val visible: Int 9 | ) -------------------------------------------------------------------------------- /common/src/main/res/anim/dd_mask_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /common/src/main/res/anim/dd_mask_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/XGlideModule.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | @GlideModule 7 | class XGlideModule : AppGlideModule() 8 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/mine/add/AddArticleState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.mine.add 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class AddArticleState( 6 | val succeed: Unit? = null, 7 | val error: Throwable? = null 8 | ) : State -------------------------------------------------------------------------------- /common/src/main/res/anim/dd_menu_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/register/RegisterState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.register 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class RegisterState( 6 | val succeed: Unit? = null, 7 | val error: Throwable? = null 8 | ) : State -------------------------------------------------------------------------------- /common/src/main/res/anim/dd_menu_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 19 16:33:02 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_setting_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/res/layout/view_status_flex.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/res/layout/view_status_web.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/ReduxUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import app.itgungnir.kwa.common.redux.AppRedux 5 | 6 | class ReduxUtil : Util { 7 | 8 | override fun init(application: Application) { 9 | AppRedux.init(application) 10 | } 11 | } -------------------------------------------------------------------------------- /common/src/main/res/layout/view_status_list.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_weixin_child.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/ArticleListResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class ArticleListResponse( 4 | val curPage: Int, 5 | val datas: List, 6 | val offset: Int, 7 | val over: Boolean, 8 | val pageCount: Int, 9 | val size: Int, 10 | val total: Int 11 | ) -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/BannerResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class BannerResponse( 4 | val desc: String, 5 | val id: Int, 6 | val imagePath: String, 7 | val isVisible: Int, 8 | val order: Int, 9 | val title: String, 10 | val type: Int, 11 | val url: String 12 | ) -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/ScheduleListResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class ScheduleListResponse( 4 | val curPage: Int, 5 | val datas: List, 6 | val offset: Int, 7 | val over: Boolean, 8 | val pageCount: Int, 9 | val size: Int, 10 | val total: Int 11 | ) -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/DateTimeUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import net.danlew.android.joda.JodaTimeAndroid 5 | 6 | class DateTimeUtil : Util { 7 | 8 | override fun init(application: Application) { 9 | JodaTimeAndroid.init(application) 10 | } 11 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_project_child.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/fragment_hierarchy_child.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/view_schedule_menu_content.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/CrashDetectUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import com.tencent.bugly.crashreport.CrashReport 5 | 6 | class CrashDetectUtil : Util { 7 | 8 | override fun init(application: Application) { 9 | 10 | CrashReport.initCrashReport(application) 11 | } 12 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_home_banner_child.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/TabResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class TabResponse( 4 | val children: List, 5 | val courseId: Int, 6 | val id: Int, 7 | val name: String, 8 | val order: Int, 9 | val parentChapterId: Int, 10 | val userControlSetTop: Boolean, 11 | val visible: Int 12 | ) -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_tree_child.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "downloadUrl": "http://oss.pgyer.com/fba405e1d907c2dd529dc751ec31c1b2.apk?Expires=1559704901&OSSAccessKeyId=uKgrdAVRfXyY0LsE&response-content-disposition=attachment%3B+filename%3Dapp-release.apk&Signature=p7qi%2FuC7QzSVMMANlet9ZVmlklc%3D", 4 | "version": "1.2.2", 5 | "versionDesc": "主要更新:\r\n优化权限申请逻辑。" 6 | }, 7 | "errorCode": 0, 8 | "errorMsg": "" 9 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_setting_button.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/LoginResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class LoginResponse( 4 | val chapterTops: List, 5 | val collectIds: Set, 6 | val email: String, 7 | val icon: String, 8 | val id: Int, 9 | val password: String, 10 | val token: String, 11 | val type: Int, 12 | val username: String 13 | ) -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/project/ProjectState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.project 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class ProjectState( 6 | val tabs: List = listOf(), 7 | val error: Throwable? = null 8 | ) : State { 9 | 10 | data class ProjectTabVO( 11 | val id: Int, 12 | val name: String 13 | ) 14 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/login/LoginState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.login 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class LoginState( 6 | val userInfo: UserInfoVO? = null, 7 | val error: Throwable? = null 8 | ) : State { 9 | 10 | data class UserInfoVO( 11 | val collectIds: Set, 12 | val userName: String 13 | ) 14 | } -------------------------------------------------------------------------------- /common/src/main/res/layout/view_status_flex_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_navigation_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/LeakDetectUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import com.squareup.leakcanary.LeakCanary 5 | 6 | class LeakDetectUtil : Util { 7 | 8 | override fun init(application: Application) { 9 | if (LeakCanary.isInAnalyzerProcess(application)) { 10 | LeakCanary.install(application) 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /app_main/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/ScheduleResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class ScheduleResponse( 4 | val completeDate: Long, 5 | val completeDateStr: String, 6 | val content: String, 7 | val date: Long, 8 | val dateStr: String, 9 | val id: Int, 10 | val priority: Int, 11 | val status: Int, 12 | val title: String, 13 | val type: Int, 14 | val userId: Int 15 | ) -------------------------------------------------------------------------------- /common/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0.5dp 5 | 6 | 24sp 7 | 18sp 8 | 16sp 9 | 14sp 10 | 12sp 11 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/weixin/WeixinState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.weixin 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class WeixinState( 6 | val tabs: List = listOf(), 7 | val currTab: WeixinTabVO? = null, 8 | val k: String = "", 9 | val error: Throwable? = null 10 | ) : State { 11 | 12 | data class WeixinTabVO( 13 | val id: Int, 14 | val name: String 15 | ) 16 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/tools/ToolsState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.tools 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class ToolsState( 7 | val items: List = listOf(), 8 | val error: Throwable? = null 9 | ) : State { 10 | 11 | data class ToolTagVO( 12 | val id: Int, 13 | val name: String, 14 | val link: String 15 | ) : ListItem 16 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/schedule/done/ScheduleDoneState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.schedule.done 2 | 3 | import app.itgungnir.kwa.support.schedule.ScheduleState 4 | import my.itgungnir.rxmvvm.core.mvvm.State 5 | 6 | data class ScheduleDoneState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean = false, 11 | val error: Throwable? = null 12 | ) : State -------------------------------------------------------------------------------- /app_support/src/main/res/layout/view_schedule_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/main/MainState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.main 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | 5 | data class MainState( 6 | val versionInfo: VersionVO? = null, 7 | val error: Throwable? = null 8 | ) : State { 9 | 10 | data class TabItem( 11 | val title: String, 12 | val unselectedIcon: String, 13 | val selectedIcon: String 14 | ) 15 | 16 | data class VersionVO( 17 | val upgradeUrl: String, 18 | val upgradeVersion: String, 19 | val upgradeDesc: String 20 | ) 21 | } -------------------------------------------------------------------------------- /app_main/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion target_sdk_version 8 | buildToolsVersion build_tools_version 9 | defaultConfig { 10 | minSdkVersion min_sdk_version 11 | targetSdkVersion target_sdk_version 12 | } 13 | } 14 | 15 | dependencies { 16 | // Project 17 | implementation project(':common') 18 | // Router 19 | kapt "com.github.ITGungnir.GRouter:router_compiler:$router_version" 20 | } 21 | -------------------------------------------------------------------------------- /app_support/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion target_sdk_version 8 | buildToolsVersion build_tools_version 9 | defaultConfig { 10 | minSdkVersion min_sdk_version 11 | targetSdkVersion target_sdk_version 12 | } 13 | } 14 | 15 | dependencies { 16 | // Project 17 | implementation project(':common') 18 | // Router 19 | kapt "com.github.ITGungnir.GRouter:router_compiler:$router_version" 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/ThemeUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import androidx.appcompat.app.AppCompatDelegate 5 | import app.itgungnir.kwa.common.redux.AppRedux 6 | 7 | class ThemeUtil : Util { 8 | 9 | override fun init(application: Application) { 10 | 11 | when (AppRedux.instance.isDarkMode()) { 12 | true -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 13 | false -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/TreeState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class TreeState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val error: Throwable? = null 10 | ) : State { 11 | 12 | data class TreeVO( 13 | val name: String, 14 | val children: List 15 | ) : ListItem { 16 | 17 | data class TreeTagVO( 18 | val id: Int, 19 | val name: String 20 | ) : ListItem 21 | } 22 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/search/SearchState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.search 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class SearchState( 7 | val items: List = listOf(), 8 | val error: Throwable? = null 9 | ) : State { 10 | 11 | data class SearchHotKeyVO( 12 | val data: List 13 | ) : ListItem 14 | 15 | data class SearchHistoryVO( 16 | val data: List 17 | ) : ListItem 18 | 19 | data class SearchTagVO( 20 | val name: String 21 | ) : ListItem 22 | } -------------------------------------------------------------------------------- /app/src/main/java/app/itgungnir/kwa/App.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa 2 | 3 | import android.content.Context 4 | import androidx.multidex.MultiDex 5 | import androidx.multidex.MultiDexApplication 6 | import app.itgungnir.kwa.common.util.AppConfig 7 | 8 | class App : MultiDexApplication() { 9 | 10 | companion object { 11 | // 是否是第一次运行项目,通过判断决定是否跳过SplashActivity 12 | var isFirstRun = true 13 | } 14 | 15 | override fun attachBaseContext(base: Context?) { 16 | super.attachBaseContext(base) 17 | MultiDex.install(this) 18 | } 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | AppConfig.instance.init(this) 23 | } 24 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/AppConfig.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | 5 | class AppConfig private constructor() { 6 | 7 | companion object { 8 | val instance by lazy { AppConfig() } 9 | } 10 | 11 | fun init(application: Application) { 12 | listOf( 13 | CacheUtil.instance, 14 | CrashDetectUtil(), 15 | DateTimeUtil(), 16 | LeakDetectUtil(), 17 | LoggingUtil(), 18 | ReduxUtil(), 19 | ScreenAdaptUtil(), 20 | ThemeUtil() 21 | ).map { 22 | it.init(application) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/redux/AppState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.redux 2 | 3 | import app.itgungnir.kwa.common.APP_VERSION 4 | 5 | data class AppState( 6 | // 搜索历史 7 | val searchHistory: Set = linkedSetOf(), 8 | // 收藏列表 9 | val collectIds: Set = setOf(), 10 | // 标识收藏列表改变 11 | val collectChanges: String = "", 12 | // 用户名 13 | val userName: String? = null, 14 | // Cookies 15 | val cookies: Set = setOf(), 16 | // 是否开启“自动缓存” 17 | val autoCache: Boolean = true, 18 | // 是否开启“无图模式” 19 | val noImage: Boolean = false, 20 | // 是否开启“夜间模式” 21 | val darkMode: Boolean = false, 22 | // 当前版本 23 | val version: String = APP_VERSION 24 | ) -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/hierarchy/HierarchyChildState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.hierarchy 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class HierarchyChildState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean = false, 11 | val error: Throwable? = null 12 | ) : State { 13 | 14 | data class HierarchyArticleVO( 15 | val id: Int, 16 | val originId: Int, 17 | val author: String, 18 | val title: String, 19 | val date: String, 20 | val link: String 21 | ) : ListItem 22 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/search_result/SearchResultState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.search_result 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class SearchResultState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean = false, 11 | val error: Throwable? = null 12 | ) : State { 13 | 14 | data class SearchResultArticleVO( 15 | val id: Int, 16 | val originId: Int, 17 | val author: String, 18 | val title: String, 19 | val date: String, 20 | val link: String 21 | ) : ListItem 22 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_schedule.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/mine/MineState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.mine 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class MineState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean? = null, 11 | val error: Throwable? = null 12 | ) : State { 13 | 14 | data class MineArticleVO( 15 | val id: Int, 16 | val originId: Int, 17 | val author: String, 18 | val category: String, 19 | val categoryId: Int, 20 | val title: String, 21 | val date: String, 22 | val link: String 23 | ) : ListItem 24 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/weixin/child/WeixinChildState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.weixin.child 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class WeixinChildState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean = false, 11 | val shouldScrollToTop: Boolean = false, 12 | val error: Throwable? = null 13 | ) : State { 14 | 15 | data class WeixinArticleVO( 16 | val id: Int, 17 | val originId: Int, 18 | val author: String, 19 | val title: String, 20 | val date: String, 21 | val link: String 22 | ) : ListItem 23 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_mine.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_tree.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_schedule_done.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_search_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /common/src/main/res/layout/view_list_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/delegate/DividerDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting.delegate 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.support.R 6 | import app.itgungnir.kwa.support.setting.SettingState 7 | import my.itgungnir.ui.easy_adapter.BaseDelegate 8 | import my.itgungnir.ui.easy_adapter.EasyAdapter 9 | 10 | class DividerDelegate : BaseDelegate() { 11 | 12 | override fun layoutId(): Int = R.layout.list_item_setting_divider 13 | 14 | override fun onCreateVH(container: View) {} 15 | 16 | override fun onBindVH( 17 | item: SettingState.DividerVO, 18 | holder: EasyAdapter.VH, 19 | position: Int, 20 | payloads: MutableList 21 | ) { 22 | } 23 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/project/child/ProjectChildState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.project.child 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class ProjectChildState( 7 | val refreshing: Boolean = false, 8 | val items: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean = false, 11 | val error: Throwable? = null 12 | ) : State { 13 | 14 | data class ProjectArticleVO( 15 | val id: Int, 16 | val originId: Int, 17 | val cover: String, 18 | val title: String, 19 | val author: String, 20 | val desc: String, 21 | val date: String, 22 | val link: String, 23 | val repositoryLink: String 24 | ) : ListItem 25 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/redux/Actions.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.redux 2 | 3 | import my.itgungnir.rxmvvm.core.redux.Action 4 | 5 | data class AddSearchHistory( 6 | val value: String 7 | ) : Action 8 | 9 | object ClearSearchHistory : Action 10 | 11 | data class LocalizeCookies( 12 | val cookies: Set 13 | ) : Action 14 | 15 | data class LocalizeUserInfo( 16 | val collectIds: Set, 17 | val userName: String 18 | ) : Action 19 | 20 | object ClearUserInfo : Action 21 | 22 | data class CollectArticle( 23 | val articleId: Int 24 | ) : Action 25 | 26 | data class DisCollectArticle( 27 | val articleId: Int 28 | ) : Action 29 | 30 | object ToggleAutoCache : Action 31 | 32 | object ToggleNoImage : Action 33 | 34 | object ToggleDarkMode : Action 35 | 36 | object UpdateVersion : Action -------------------------------------------------------------------------------- /common/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 22 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/LoggingUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import app.itgungnir.kwa.common.BuildConfig 5 | import app.itgungnir.kwa.common.HTTP_LOG_TAG 6 | import com.orhanobut.logger.AndroidLogAdapter 7 | import com.orhanobut.logger.Logger 8 | import com.orhanobut.logger.PrettyFormatStrategy 9 | 10 | class LoggingUtil : Util { 11 | 12 | override fun init(application: Application) { 13 | 14 | val logStrategy = PrettyFormatStrategy.newBuilder() 15 | .showThreadInfo(false) 16 | .methodOffset(4) 17 | .tag(HTTP_LOG_TAG) 18 | .build() 19 | 20 | Logger.addLogAdapter(object : AndroidLogAdapter(logStrategy) { 21 | override fun isLoggable(priority: Int, tag: String?) = BuildConfig.DEBUG 22 | }) 23 | } 24 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/navigation/NavigationState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.navigation 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class NavigationState( 7 | val tabs: List = listOf(), 8 | val items: List = listOf(), 9 | val error: Throwable? = null 10 | ) : State { 11 | 12 | data class NavTabVO( 13 | val name: String, 14 | val selected: Boolean = false 15 | ) : ListItem 16 | 17 | data class NavigationVO( 18 | val title: String, 19 | val children: List = listOf() 20 | ) : ListItem { 21 | 22 | data class NavTagVO( 23 | val id: Int, 24 | val originId: Int, 25 | val name: String, 26 | val link: String 27 | ) : ListItem 28 | } 29 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/Constant.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common 2 | 3 | // 路由表 4 | const val SplashActivity = "splash" 5 | const val MainActivity = "main" 6 | const val SearchResultActivity = "searchResult" 7 | const val WebActivity = "web" 8 | const val HierarchyActivity = "hierarchy" 9 | const val LoginActivity = "login" 10 | const val RegisterActivity = "register" 11 | const val SettingActivity = "setting" 12 | const val ScheduleActivity = "schedule" 13 | const val ScheduleDoneActivity = "scheduleDone" 14 | 15 | // HTTP 16 | const val APP_VERSION = "1.2.2" 17 | const val HTTP_BASE_URL = "https://www.wanandroid.com" 18 | const val HTTP_VERSION_URL = "https://raw.githubusercontent.com" 19 | const val HTTP_TIME_OUT = 5L 20 | const val HTTP_LOG_TAG = "kotlin-wan-android" 21 | const val MAX_CACHE_SIZE = 1024 * 1024 * 50L 22 | 23 | // 系统配置 24 | const val ADAPT_WIDTH = 375.0F -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/http/HttpExt.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.http 2 | 3 | import io.reactivex.Single 4 | import io.reactivex.SingleTransformer 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | 8 | inline fun Single>.handleResult() = compose { upsteam -> 9 | upsteam.flatMap { 10 | when (it.errorCode) { 11 | 0 -> 12 | Single.just(it.data ?: T::class.java.newInstance()) 13 | else -> 14 | Single.error(HttpException(it.errorMsg)) 15 | } 16 | } 17 | }!! 18 | 19 | fun Single.io2Main(): Single { 20 | val transformer: SingleTransformer = SingleTransformer { 21 | it.subscribeOn(Schedulers.io()) 22 | .observeOn(AndroidSchedulers.mainThread()) 23 | } 24 | return compose(transformer) 25 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_navigation_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/dto/ArticleResponse.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.dto 2 | 3 | data class ArticleResponse( 4 | val apkLink: String, 5 | val author: String, 6 | val chapterId: Int, 7 | val chapterName: String, 8 | val collect: Boolean, 9 | val courseId: Int, 10 | val desc: String, 11 | val envelopePic: String, 12 | val fresh: Boolean, 13 | val id: Int, 14 | val link: String, 15 | val niceDate: String, 16 | val origin: String, 17 | val originId: Int, 18 | val projectLink: String, 19 | val publishTime: Long, 20 | val superChapterId: Int, 21 | val superChapterName: String, 22 | val tags: List, 23 | val title: String, 24 | val type: Int, 25 | val userId: Int, 26 | val visible: Int, 27 | val zan: Int 28 | ) { 29 | 30 | data class Tag( 31 | val name: String, 32 | val url: String 33 | ) 34 | } -------------------------------------------------------------------------------- /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=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | android.enableJetifier=true 17 | android.useAndroidX=true 18 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/SettingState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class SettingState( 7 | val items: List = listOf(), 8 | val error: Throwable? = null 9 | ) : State { 10 | 11 | object DividerVO : ListItem 12 | 13 | data class CheckableVO( 14 | val id: Int, 15 | val iconFont: String, 16 | val title: String, 17 | val isChecked: Boolean = false 18 | ) : ListItem 19 | 20 | data class NavigableVO( 21 | val id: Int, 22 | val iconFont: String, 23 | val title: String 24 | ) : ListItem 25 | 26 | data class DigitalVO( 27 | val id: Int, 28 | val iconFont: String, 29 | val title: String, 30 | val digit: String 31 | ) : ListItem 32 | 33 | object ButtonVO : ListItem 34 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_project.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_hierarchy.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /common/src/main/res/layout/view_status_list_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_search_hot.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/schedule/ScheduleState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.schedule 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class ScheduleState( 7 | val refreshing: Boolean = false, 8 | val type: Int? = null, 9 | val priority: Int? = null, 10 | val orderBy: Int = 4, 11 | val dismissFlag: Unit? = null, 12 | val items: List = listOf(), 13 | val loading: Boolean = false, 14 | val hasMore: Boolean = false, 15 | val error: Throwable? = null 16 | ) : State { 17 | 18 | data class ScheduleVO( 19 | val id: Int, 20 | val title: String, 21 | val content: String, 22 | val targetDate: String, 23 | val type: Int, 24 | val priority: Int 25 | ) : ListItem 26 | 27 | data class MenuTabVO( 28 | val title: String, 29 | val value: Int?, 30 | val selected: Boolean = false 31 | ) : ListItem 32 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/AboutUsDialog.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import app.itgungnir.kwa.common.redux.AppRedux 9 | import app.itgungnir.kwa.support.R 10 | import kotlinx.android.synthetic.main.dialog_about_us.* 11 | import my.itgungnir.ui.dialog.NoTitleDialogFragment 12 | 13 | class AboutUsDialog : NoTitleDialogFragment() { 14 | 15 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = 16 | inflater.inflate(R.layout.dialog_about_us, container, false) 17 | 18 | @SuppressLint("SetTextI18n") 19 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 20 | super.onViewCreated(view, savedInstanceState) 21 | 22 | versionInfo.text = "V${AppRedux.instance.currState().version}" 23 | } 24 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_main_bottom_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/HomeState.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home 2 | 3 | import my.itgungnir.rxmvvm.core.mvvm.State 4 | import my.itgungnir.ui.easy_adapter.ListItem 5 | 6 | data class HomeState( 7 | val refreshing: Boolean = false, 8 | val dataList: List = listOf(), 9 | val loading: Boolean = false, 10 | val hasMore: Boolean = false, 11 | val error: Throwable? = null 12 | ) : State { 13 | 14 | data class BannerVO( 15 | val items: List = listOf() 16 | ) : ListItem { 17 | 18 | data class BannerItem( 19 | val id: Int, 20 | val url: String, 21 | val title: String, 22 | val target: String 23 | ) 24 | } 25 | 26 | data class HomeArticleVO( 27 | val id: Int, 28 | val originId: Int, 29 | val author: String, 30 | val category: String, 31 | val categoryId: Int, 32 | val title: String, 33 | val date: String, 34 | val link: String, 35 | val isTop: Boolean = false 36 | ) : ListItem 37 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/register/RegisterViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.register 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class RegisterViewModel : BaseViewModel(initialState = RegisterState()) { 10 | 11 | @SuppressLint("CheckResult") 12 | fun register(userName: String, password: String, confirmPwd: String) { 13 | HttpClient.api.register(userName, password, confirmPwd) 14 | .handleResult() 15 | .io2Main() 16 | .subscribe({ 17 | setState { 18 | copy( 19 | succeed = Unit, 20 | error = null 21 | ) 22 | } 23 | }, { 24 | setState { 25 | copy( 26 | error = it 27 | ) 28 | } 29 | }) 30 | } 31 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.login 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class LoginViewModel : BaseViewModel(initialState = LoginState()) { 10 | 11 | @SuppressLint("CheckResult") 12 | fun login(userName: String, password: String) { 13 | HttpClient.api.login(userName, password) 14 | .handleResult() 15 | .io2Main() 16 | .subscribe({ 17 | setState { 18 | copy( 19 | userInfo = LoginState.UserInfoVO(collectIds = it.collectIds, userName = it.username), 20 | error = null 21 | ) 22 | } 23 | }, { 24 | setState { 25 | copy( 26 | error = it 27 | ) 28 | } 29 | }) 30 | } 31 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/project/ProjectViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.project 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class ProjectViewModel : BaseViewModel(initialState = ProjectState()) { 10 | 11 | /** 12 | * 获取项目分类 13 | */ 14 | @SuppressLint("CheckResult") 15 | fun getProjectTabs() { 16 | HttpClient.api.projectTabs() 17 | .handleResult() 18 | .io2Main() 19 | .map { it.map { item -> ProjectState.ProjectTabVO(item.id, item.name) } } 20 | .subscribe({ 21 | setState { 22 | copy( 23 | tabs = it, 24 | error = null 25 | ) 26 | } 27 | }, { 28 | setState { 29 | copy( 30 | error = it 31 | ) 32 | } 33 | }) 34 | } 35 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/delegate/ButtonDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting.delegate 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.support.R 6 | import app.itgungnir.kwa.support.setting.SettingState 7 | import kotlinx.android.synthetic.main.list_item_setting_button.view.* 8 | import my.itgungnir.ui.easy_adapter.BaseDelegate 9 | import my.itgungnir.ui.easy_adapter.EasyAdapter 10 | 11 | class ButtonDelegate( 12 | private val callback: () -> Unit 13 | ) : BaseDelegate() { 14 | 15 | override fun layoutId(): Int = R.layout.list_item_setting_button 16 | 17 | override fun onCreateVH(container: View) { 18 | container.apply { 19 | logout.apply { 20 | ready("退出登录") 21 | setOnClickListener { 22 | this@ButtonDelegate.callback.invoke() 23 | } 24 | } 25 | } 26 | } 27 | 28 | override fun onBindVH( 29 | item: SettingState.ButtonVO, 30 | holder: EasyAdapter.VH, 31 | position: Int, 32 | payloads: MutableList 33 | ) { 34 | } 35 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/redux/AppRedux.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.redux 2 | 3 | import android.app.Application 4 | import com.google.gson.Gson 5 | import my.itgungnir.rxmvvm.core.redux.BaseRedux 6 | 7 | class AppRedux(context: Application) : BaseRedux( 8 | context = context, 9 | initialState = AppState(), 10 | reducer = AppReducer(), 11 | middlewareList = listOf() 12 | ) { 13 | 14 | companion object { 15 | 16 | lateinit var instance: AppRedux 17 | 18 | fun init(context: Application) { 19 | instance = AppRedux(context) 20 | } 21 | } 22 | 23 | override fun deserializeToCurrState(json: String): AppState? = 24 | Gson().fromJson(json, AppState::class.java) 25 | 26 | // 判断用户是否已登录 27 | fun isUserIn() = !currState().userName.isNullOrBlank() 28 | 29 | // 判断用户是否已收藏了某篇文章 30 | fun isCollected(articleId: Int) = currState().collectIds.contains(articleId) 31 | 32 | // 判断当前是否自动缓存 33 | fun isAutoCache() = currState().autoCache 34 | 35 | // 判断当前是否处于无图模式 36 | fun isNoImage() = currState().noImage 37 | 38 | // 判断当前是否处于夜间模式 39 | fun isDarkMode() = currState().darkMode 40 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/dialog_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/weixin/WeixinViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.weixin 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class WeixinViewModel : BaseViewModel(initialState = WeixinState()) { 10 | 11 | /** 12 | * 获取公众号分类 13 | */ 14 | @SuppressLint("CheckResult") 15 | fun getWeixinTabs() { 16 | HttpClient.api.weixinTabs() 17 | .handleResult() 18 | .io2Main() 19 | .map { it.map { item -> WeixinState.WeixinTabVO(item.id, item.name) } } 20 | .subscribe({ 21 | setState { 22 | copy( 23 | tabs = it, 24 | currTab = it[0], 25 | error = null 26 | ) 27 | } 28 | }, { 29 | setState { 30 | copy( 31 | error = it 32 | ) 33 | } 34 | }) 35 | } 36 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/fragment_weixin.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 25 | 26 | 30 | 31 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/delegate/NavigableDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting.delegate 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.support.R 6 | import app.itgungnir.kwa.support.setting.SettingState 7 | import kotlinx.android.synthetic.main.list_item_setting_navigable.view.* 8 | import my.itgungnir.ui.easy_adapter.BaseDelegate 9 | import my.itgungnir.ui.easy_adapter.EasyAdapter 10 | import my.itgungnir.ui.onAntiShakeClick 11 | 12 | class NavigableDelegate( 13 | private val navigateCallback: (Int) -> Unit 14 | ) : BaseDelegate() { 15 | 16 | override fun layoutId(): Int = R.layout.list_item_setting_navigable 17 | 18 | override fun onCreateVH(container: View) { 19 | } 20 | 21 | override fun onBindVH( 22 | item: SettingState.NavigableVO, 23 | holder: EasyAdapter.VH, 24 | position: Int, 25 | payloads: MutableList 26 | ) { 27 | 28 | holder.render(item) { 29 | 30 | this.onAntiShakeClick(2000L) { 31 | navigateCallback.invoke(item.id) 32 | } 33 | 34 | iconView.text = item.iconFont 35 | 36 | titleView.text = item.title 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/mine/add/AddArticleViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.mine.add 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import app.itgungnir.kwa.common.redux.AppRedux 8 | import app.itgungnir.kwa.common.redux.CollectArticle 9 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 10 | 11 | class AddArticleViewModel : BaseViewModel(initialState = AddArticleState()) { 12 | 13 | /** 14 | * 收藏站外文章 15 | */ 16 | @SuppressLint("CheckResult") 17 | fun addArticle(title: String, link: String) { 18 | HttpClient.api.outerCollect(title, "站外收藏", link) 19 | .handleResult() 20 | .io2Main() 21 | .subscribe({ 22 | AppRedux.instance.dispatch(CollectArticle(it.id)) 23 | setState { 24 | copy( 25 | succeed = Unit, 26 | error = null 27 | ) 28 | } 29 | }, { 30 | setState { 31 | copy( 32 | error = it 33 | ) 34 | } 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/tools/ToolsViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.tools 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class ToolsViewModel : BaseViewModel(initialState = ToolsState()) { 10 | 11 | @SuppressLint("CheckResult") 12 | fun getTools() { 13 | HttpClient.api.tools() 14 | .handleResult() 15 | .io2Main() 16 | .map { 17 | it.map { item -> 18 | ToolsState.ToolTagVO( 19 | id = item.id, 20 | name = item.name, 21 | link = item.link 22 | ) 23 | } 24 | } 25 | .subscribe({ 26 | setState { 27 | copy( 28 | items = it, 29 | error = null 30 | ) 31 | } 32 | }, { 33 | setState { 34 | copy( 35 | error = it 36 | ) 37 | } 38 | }) 39 | } 40 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /common/src/main/res/layout/view_status_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | 34 | 35 | -------------------------------------------------------------------------------- /config.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | // Build 3 | gradle_version = '3.4.1' 4 | target_sdk_version = 28 5 | build_tools_version = '28.0.3' 6 | min_sdk_version = 19 7 | version_code = 6 8 | version_name = '1.2.2' 9 | // Kotlin 10 | kotlin_version = '1.3.31' 11 | anko_version = '0.10.8' 12 | // Support 13 | appcompat_version = '1.0.2' 14 | material_version = '1.0.0' 15 | recyclerview_version = '1.0.0' 16 | constraintlayout_version = '1.1.3' 17 | // Network 18 | okhttp_version = '3.12.0' 19 | retrofit_version = '2.5.0' 20 | // ReactiveX 21 | rxjava_version = '2.2.4' 22 | rxandroid_version = '2.1.1' 23 | rxbinding_version = '2.2.0' 24 | // Glide 25 | glide_version = '4.8.0' 26 | glide_trans_version = '3.3.0' 27 | // Widgets 28 | flex_version = '1.0.0' 29 | // ITGungnir 30 | uikit_version = '1.0.5' 31 | rxmvvm_version = '1.3.0' 32 | router_version = '1.1.0' 33 | permission_version = '0.1.0' 34 | // Tools 35 | joda_version = '2.10.1' 36 | gson_version = '2.8.5' 37 | logger_version = '2.2.0' 38 | dex_version = '2.0.1' 39 | bugly_sdk_version = '2.8.6.0' 40 | leak_canary_version = '1.6.3' 41 | } 42 | 43 | static def addRepos(RepositoryHandler handler) { 44 | handler.google() 45 | handler.jcenter() 46 | handler.maven { url 'https://jitpack.io' } 47 | } 48 | 49 | ext.addRepos = this.&addRepos 50 | -------------------------------------------------------------------------------- /common/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FFFFFFFF 5 | #FF707070 6 | #FFFF8F00 7 | 8 | #FFC2C2C2 9 | #FFF5F5F5 10 | #99707070 11 | #FF707070 12 | #FFC2C2C2 13 | #FFB4004E 14 | #FF087F23 15 | #FF007AC1 16 | #FFFF5722 17 | #FF29B6F6 18 | #FFFFFFFF 19 | #FFFFFFFF 20 | #FF707070 21 | #FFC2C2C2 22 | 23 | #FF373737 24 | #FF707070 25 | #FFAEAEAE 26 | #FFFFFFFF 27 | #FFFFFFFF 28 | #FFF5F5F5 29 | 30 | -------------------------------------------------------------------------------- /common/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FF0D1D34 5 | #FF153159 6 | #FFC56000 7 | 8 | #FF497090 9 | #FF112444 10 | #99707070 11 | #FF3E6091 12 | #FF001138 13 | #FFB4004E 14 | #FF087F23 15 | #FF007AC1 16 | #FFFF5722 17 | #FF29B6F6 18 | #FF023259 19 | #FF013763 20 | #FF497090 21 | #FF112444 22 | 23 | #FF789EC0 24 | #FF6487A5 25 | #FF497090 26 | #FF789EC0 27 | #FFCDCDCD 28 | #FF497090 29 | 30 | -------------------------------------------------------------------------------- /app_support/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 17 | 18 | 22 | 23 | 27 | 28 | 31 | 32 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.main 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import app.itgungnir.kwa.common.redux.AppRedux 8 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 9 | 10 | class MainViewModel : BaseViewModel(initialState = MainState()) { 11 | 12 | @SuppressLint("CheckResult") 13 | fun getLatestVersion() { 14 | HttpClient.api2.versionInfo() 15 | .handleResult() 16 | .io2Main() 17 | .subscribe({ 18 | if (it.version > AppRedux.instance.currState().version) { 19 | setState { 20 | copy( 21 | versionInfo = MainState.VersionVO( 22 | upgradeUrl = it.downloadUrl, 23 | upgradeVersion = it.version, 24 | upgradeDesc = it.versionDesc 25 | ), 26 | error = null 27 | ) 28 | } 29 | } 30 | }, { 31 | setState { 32 | copy( 33 | versionInfo = null, 34 | error = it 35 | ) 36 | } 37 | }) 38 | } 39 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/delegate/CheckableDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting.delegate 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.support.R 6 | import app.itgungnir.kwa.support.setting.SettingState 7 | import kotlinx.android.synthetic.main.list_item_setting_checkable.view.* 8 | import my.itgungnir.ui.easy_adapter.BaseDelegate 9 | import my.itgungnir.ui.easy_adapter.EasyAdapter 10 | 11 | class CheckableDelegate( 12 | private val checkCallback: (Int) -> Unit 13 | ) : BaseDelegate() { 14 | 15 | override fun layoutId(): Int = R.layout.list_item_setting_checkable 16 | 17 | override fun onCreateVH(container: View) { 18 | } 19 | 20 | override fun onBindVH( 21 | item: SettingState.CheckableVO, 22 | holder: EasyAdapter.VH, 23 | position: Int, 24 | payloads: MutableList 25 | ) { 26 | 27 | holder.render(item) { 28 | 29 | checkerView.apply { 30 | isChecked = if (payloads.isNotEmpty()) { 31 | payloads[0].getBoolean("PL_CHECKED") 32 | } else { 33 | item.isChecked 34 | } 35 | setOnClickListener { 36 | checkCallback.invoke(item.id) 37 | } 38 | } 39 | 40 | iconView.text = item.iconFont 41 | 42 | titleView.text = item.title 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/delegate/DigitalDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting.delegate 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.common.redux.AppRedux 6 | import app.itgungnir.kwa.support.R 7 | import app.itgungnir.kwa.support.setting.SettingState 8 | import kotlinx.android.synthetic.main.list_item_setting_digital.view.* 9 | import my.itgungnir.ui.easy_adapter.BaseDelegate 10 | import my.itgungnir.ui.easy_adapter.EasyAdapter 11 | import my.itgungnir.ui.onAntiShakeClick 12 | 13 | class DigitalDelegate( 14 | private val digitalClickCallback: (Int) -> Unit 15 | ) : BaseDelegate() { 16 | 17 | override fun layoutId(): Int = R.layout.list_item_setting_digital 18 | 19 | override fun onCreateVH(container: View) { 20 | } 21 | 22 | override fun onBindVH( 23 | item: SettingState.DigitalVO, 24 | holder: EasyAdapter.VH, 25 | position: Int, 26 | payloads: MutableList 27 | ) { 28 | 29 | holder.render(item) { 30 | 31 | this.onAntiShakeClick(2000L) { 32 | digitalClickCallback.invoke(item.id) 33 | } 34 | 35 | iconView.text = item.iconFont 36 | 37 | titleView.text = item.title 38 | 39 | digitView.text = when { 40 | !AppRedux.instance.isAutoCache() -> "0KB" 41 | payloads.isNotEmpty() -> payloads[0].getString("PL_DIGIT") ?: item.digit 42 | else -> item.digit 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/dialog_about_us.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 20 | 21 | 29 | 30 | 38 | 39 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/search/SearchViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.search 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import app.itgungnir.kwa.common.redux.AppRedux 8 | import io.reactivex.Single 9 | import io.reactivex.functions.BiFunction 10 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 11 | 12 | class SearchViewModel : BaseViewModel(initialState = SearchState()) { 13 | 14 | /** 15 | * 初始化数据 16 | */ 17 | @SuppressLint("CheckResult") 18 | fun initData() { 19 | 20 | val s1 = HttpClient.api.hotKeys() 21 | .handleResult() 22 | .io2Main() 23 | .map { SearchState.SearchHotKeyVO(data = it.map { item -> SearchState.SearchTagVO(item.name) }) } 24 | 25 | val s2 = Single.just(AppRedux.instance.currState().searchHistory) 26 | .map { SearchState.SearchHistoryVO(data = it.map { item -> SearchState.SearchTagVO(item) }) } 27 | 28 | Single.zip(s1, s2, BiFunction { t1: SearchState.SearchHotKeyVO, t2: SearchState.SearchHistoryVO -> 29 | listOf(t1, t2) 30 | }).subscribe({ 31 | setState { 32 | copy( 33 | items = it, 34 | error = null 35 | ) 36 | } 37 | }, { 38 | setState { 39 | copy( 40 | error = it 41 | ) 42 | } 43 | }) 44 | } 45 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'grouter' 6 | 7 | android { 8 | compileSdkVersion target_sdk_version 9 | buildToolsVersion build_tools_version 10 | defaultConfig { 11 | applicationId "app.itgungnir.kwa" 12 | minSdkVersion min_sdk_version 13 | targetSdkVersion target_sdk_version 14 | versionCode version_code 15 | versionName version_name 16 | multiDexEnabled true 17 | } 18 | signingConfigs { 19 | config { 20 | storeFile file('D:/CodingTools/Android/itgungnir.jks') 21 | storePassword 'android' 22 | keyAlias 'android' 23 | keyPassword 'android' 24 | } 25 | } 26 | buildTypes { 27 | debug { 28 | signingConfig signingConfigs.config 29 | } 30 | release { 31 | signingConfig signingConfigs.config 32 | minifyEnabled true 33 | shrinkResources true 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | } 42 | 43 | dependencies { 44 | implementation fileTree(dir: 'libs', include: ['*.jar']) 45 | // Project 46 | implementation project(':app_main') 47 | implementation project(':app_support') 48 | implementation project(':common') 49 | // Router 50 | kapt "com.github.ITGungnir.GRouter:router_compiler:$router_version" 51 | } 52 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/dialog_add_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 24 | 25 | 34 | 35 | 41 | 42 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/schedule/menu/MenuView.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.schedule.menu 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import android.widget.LinearLayout 7 | import app.itgungnir.kwa.support.R 8 | import app.itgungnir.kwa.support.schedule.ScheduleState 9 | import kotlinx.android.synthetic.main.view_schedule_menu.view.* 10 | import my.itgungnir.ui.easy_adapter.EasyAdapter 11 | import my.itgungnir.ui.easy_adapter.bind 12 | 13 | class MenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : 14 | LinearLayout(context, attrs, defStyleAttr) { 15 | 16 | var selectCallback: ((position: Int, title: String, rentType: Int?) -> Unit)? = null 17 | 18 | private var dataList = mutableListOf() 19 | 20 | private var listAdapter: EasyAdapter? = null 21 | 22 | init { 23 | View.inflate(context, R.layout.view_schedule_menu, this) 24 | 25 | listAdapter = list.bind(diffAnalyzer = menuItemDiffer) 26 | .addDelegate({ true }, MenuItemDelegate(clickCallback = { position, vo -> 27 | dataList = dataList.mapIndexed { index, item -> 28 | item.copy(selected = index == position) 29 | }.toMutableList() 30 | listAdapter?.update(dataList) 31 | selectCallback?.invoke(position, vo.title, vo.value) 32 | })) 33 | .initialize() 34 | 35 | listAdapter?.update(dataList) 36 | } 37 | 38 | fun bind(vararg items: ScheduleState.MenuTabVO) { 39 | dataList.clear() 40 | dataList.addAll(items) 41 | listAdapter?.update(dataList) 42 | } 43 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_setting_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 29 | 30 | 39 | 40 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/search/SearchHotKeyDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.search 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.widget.TextView 6 | import app.itgungnir.kwa.main.R 7 | import kotlinx.android.synthetic.main.list_item_search_hot.view.* 8 | import my.itgungnir.ui.color 9 | import my.itgungnir.ui.easy_adapter.BaseDelegate 10 | import my.itgungnir.ui.easy_adapter.EasyAdapter 11 | import my.itgungnir.ui.onAntiShakeClick 12 | import org.jetbrains.anko.textColor 13 | 14 | class SearchHotKeyDelegate( 15 | private val keyClickCallback: (String) -> Unit 16 | ) : BaseDelegate() { 17 | 18 | override fun layoutId(): Int = R.layout.list_item_search_hot 19 | 20 | override fun onCreateVH(container: View) { 21 | container.apply { 22 | // Flex Layout 23 | childrenView.bind( 24 | layoutId = R.layout.list_item_tag, 25 | render = { view, data -> 26 | view.findViewById(R.id.tagView).apply { 27 | text = data.name 28 | textColor = this.context.color(R.color.colorAccent) 29 | onAntiShakeClick(2000L) { 30 | keyClickCallback.invoke(data.name) 31 | } 32 | } 33 | } 34 | ) 35 | } 36 | } 37 | 38 | override fun onBindVH( 39 | item: SearchState.SearchHotKeyVO, 40 | holder: EasyAdapter.VH, 41 | position: Int, 42 | payloads: MutableList 43 | ) { 44 | 45 | holder.render(item) { 46 | 47 | childrenView.refresh(item.data) 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/TreeViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class TreeViewModel : BaseViewModel(initialState = TreeState()) { 10 | 11 | /** 12 | * 获取知识体系 13 | */ 14 | @SuppressLint("CheckResult") 15 | fun getTreeList() { 16 | HttpClient.api.hierarchyTabs() 17 | .handleResult() 18 | .io2Main() 19 | .doOnSubscribe { 20 | setState { 21 | copy( 22 | refreshing = true, 23 | error = null 24 | ) 25 | } 26 | } 27 | .subscribe({ 28 | setState { 29 | copy( 30 | refreshing = false, 31 | items = it.map { item -> 32 | TreeState.TreeVO( 33 | name = item.name, 34 | children = item.children.map { child -> 35 | TreeState.TreeVO.TreeTagVO(id = child.id, name = child.name) 36 | } 37 | ) 38 | }, 39 | error = null 40 | ) 41 | } 42 | }, { 43 | setState { 44 | copy( 45 | refreshing = false, 46 | error = it 47 | ) 48 | } 49 | }) 50 | } 51 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/dialog_tools.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 23 | 24 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 28 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/TreeDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.widget.TextView 6 | import app.itgungnir.kwa.common.HierarchyActivity 7 | import app.itgungnir.kwa.main.R 8 | import com.google.gson.Gson 9 | import kotlinx.android.synthetic.main.list_item_tree.view.* 10 | import my.itgungnir.grouter.api.Router 11 | import my.itgungnir.ui.easy_adapter.BaseDelegate 12 | import my.itgungnir.ui.easy_adapter.EasyAdapter 13 | import my.itgungnir.ui.html 14 | import my.itgungnir.ui.onAntiShakeClick 15 | 16 | class TreeDelegate : BaseDelegate() { 17 | 18 | override fun layoutId(): Int = R.layout.list_item_tree 19 | 20 | override fun onCreateVH(container: View) { 21 | // Flex Layout 22 | container.apply { 23 | childrenView.bind( 24 | layoutId = R.layout.list_item_tree_child, 25 | render = { view, data -> 26 | view.findViewById(R.id.nameView).text = html(data.name) 27 | } 28 | ) 29 | } 30 | } 31 | 32 | override fun onBindVH( 33 | item: TreeState.TreeVO, 34 | holder: EasyAdapter.VH, 35 | position: Int, 36 | payloads: MutableList 37 | ) { 38 | 39 | holder.render(item) { 40 | 41 | this.onAntiShakeClick(2000L) { 42 | val json = Gson().toJson(item) 43 | Router.instance.with(context) 44 | .target(HierarchyActivity) 45 | .addParam("json", json) 46 | .go() 47 | } 48 | 49 | titleView.text = html(item.name) 50 | 51 | childrenView.refresh(item.children) 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/hierarchy/HierarchyActivity.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.hierarchy 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentStatePagerAdapter 5 | import app.itgungnir.kwa.common.HierarchyActivity 6 | import app.itgungnir.kwa.support.R 7 | import com.google.gson.Gson 8 | import kotlinx.android.synthetic.main.activity_hierarchy.* 9 | import my.itgungnir.grouter.annotation.Route 10 | import my.itgungnir.rxmvvm.core.mvvm.BaseActivity 11 | import my.itgungnir.ui.easy_adapter.ListItem 12 | import my.itgungnir.ui.html 13 | 14 | @Route(HierarchyActivity) 15 | class HierarchyActivity : BaseActivity() { 16 | 17 | override fun layoutId(): Int = R.layout.activity_hierarchy 18 | 19 | override fun initComponent() { 20 | val json = intent.extras?.getString("json") 21 | val vo = Gson().fromJson(json, TreeVO::class.java) 22 | 23 | headBar.title(vo.name) 24 | .back(getString(R.string.icon_back)) { finish() } 25 | 26 | tabLayout.setupWithViewPager(viewPager) 27 | 28 | viewPager.adapter = object : FragmentStatePagerAdapter(supportFragmentManager) { 29 | override fun getItem(position: Int): Fragment = 30 | HierarchyChildFragment.newInstance(vo.children[position].id) 31 | 32 | override fun getCount(): Int = 33 | vo.children.size 34 | 35 | override fun getPageTitle(position: Int) = 36 | html(vo.children[position].name) 37 | } 38 | } 39 | 40 | override fun observeVM() { 41 | } 42 | 43 | private data class TreeVO( 44 | val name: String, 45 | val children: List 46 | ) : ListItem { 47 | 48 | data class TreeTagVO( 49 | val id: Int, 50 | val name: String 51 | ) : ListItem 52 | } 53 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/navigation/SideBarDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.navigation 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.main.R 6 | import kotlinx.android.synthetic.main.list_item_navigation_left.view.* 7 | import my.itgungnir.ui.color 8 | import my.itgungnir.ui.easy_adapter.BaseDelegate 9 | import my.itgungnir.ui.easy_adapter.EasyAdapter 10 | import my.itgungnir.ui.onAntiShakeClick 11 | import org.jetbrains.anko.backgroundColor 12 | 13 | class SideBarDelegate( 14 | private val tabClickCallback: (Int) -> Unit 15 | ) : BaseDelegate() { 16 | 17 | override fun layoutId(): Int = R.layout.list_item_navigation_left 18 | 19 | override fun onCreateVH(container: View) { 20 | } 21 | 22 | override fun onBindVH( 23 | item: NavigationState.NavTabVO, 24 | holder: EasyAdapter.VH, 25 | position: Int, 26 | payloads: MutableList 27 | ) { 28 | 29 | holder.render(item) { 30 | 31 | this.onAntiShakeClick(2000L) { 32 | tabClickCallback.invoke(holder.adapterPosition) 33 | } 34 | 35 | nameView.apply { 36 | text = item.name 37 | backgroundColor = if (payloads.isNullOrEmpty()) { 38 | when (item.selected) { 39 | true -> this.context.color(R.color.colorPure) 40 | else -> this.context.color(R.color.clr_background) 41 | } 42 | } else { 43 | val payload = payloads[0] 44 | when (payload["PL_SELECT"]) { 45 | true -> this.context.color(R.color.colorPure) 46 | else -> this.context.color(R.color.clr_background) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_home_banner.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 23 | 24 | 34 | 35 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/web/WebViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.web 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import app.itgungnir.kwa.common.redux.AppRedux 8 | import app.itgungnir.kwa.common.redux.CollectArticle 9 | import app.itgungnir.kwa.common.redux.DisCollectArticle 10 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 11 | 12 | @SuppressLint("CheckResult") 13 | class WebViewModel : BaseViewModel(initialState = WebState()) { 14 | 15 | /** 16 | * 收藏站内文章 17 | */ 18 | fun collectInnerArticle(articleId: Int) { 19 | HttpClient.api.innerCollect(articleId) 20 | .handleResult() 21 | .io2Main() 22 | .subscribe({ 23 | AppRedux.instance.dispatch(CollectArticle(articleId)) 24 | setState { 25 | copy( 26 | error = null 27 | ) 28 | } 29 | }, { 30 | setState { 31 | copy( 32 | error = it 33 | ) 34 | } 35 | }) 36 | } 37 | 38 | /** 39 | * 取消收藏站内文章 40 | */ 41 | fun disCollectInnerArticle(articleId: Int) { 42 | HttpClient.api.innerDisCollect(articleId) 43 | .handleResult() 44 | .io2Main() 45 | .subscribe({ 46 | AppRedux.instance.dispatch(DisCollectArticle(articleId)) 47 | setState { 48 | copy( 49 | error = null 50 | ) 51 | } 52 | }, { 53 | setState { 54 | copy( 55 | error = it 56 | ) 57 | } 58 | }) 59 | } 60 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/ScreenAdaptUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.res.Resources 6 | import android.os.Bundle 7 | import app.itgungnir.kwa.common.ADAPT_WIDTH 8 | 9 | class ScreenAdaptUtil : Util { 10 | 11 | override fun init(application: Application) { 12 | application.registerActivityLifecycleCallbacks(AppActivityLifecycleCallback(application)) 13 | } 14 | 15 | inner class AppActivityLifecycleCallback(private val application: Application) : 16 | Application.ActivityLifecycleCallbacks { 17 | 18 | override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { 19 | activity?.let { 20 | val systemDM = Resources.getSystem().displayMetrics 21 | val appDM = application.resources.displayMetrics 22 | val activityDM = it.resources.displayMetrics 23 | 24 | activityDM.apply { 25 | density = systemDM.widthPixels / ADAPT_WIDTH 26 | scaledDensity = systemDM.density 27 | densityDpi = (160 * systemDM.density).toInt() 28 | } 29 | 30 | appDM.apply { 31 | density = systemDM.density 32 | scaledDensity = systemDM.scaledDensity 33 | densityDpi = systemDM.densityDpi 34 | } 35 | } 36 | } 37 | 38 | override fun onActivityStarted(activity: Activity?) {} 39 | 40 | override fun onActivityResumed(activity: Activity?) {} 41 | 42 | override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {} 43 | 44 | override fun onActivityPaused(activity: Activity?) {} 45 | 46 | override fun onActivityStopped(activity: Activity?) {} 47 | 48 | override fun onActivityDestroyed(activity: Activity?) {} 49 | } 50 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/redux/AppReducer.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.redux 2 | 3 | import app.itgungnir.kwa.common.APP_VERSION 4 | import my.itgungnir.rxmvvm.core.redux.Action 5 | import my.itgungnir.rxmvvm.core.redux.Reducer 6 | import java.util.* 7 | 8 | class AppReducer : Reducer { 9 | 10 | override fun reduce(state: AppState, action: Action): AppState = when (action) { 11 | // 添加搜索历史 12 | is AddSearchHistory -> 13 | state.copy(searchHistory = state.searchHistory + action.value) 14 | // 清空搜索历史 15 | is ClearSearchHistory -> 16 | state.copy(searchHistory = linkedSetOf()) 17 | // 持久化Cookies 18 | is LocalizeCookies -> 19 | state.copy(cookies = action.cookies) 20 | // 持久化用户名 21 | is LocalizeUserInfo -> 22 | state.copy(collectIds = action.collectIds, userName = action.userName, collectChanges = uuid()) 23 | // 清空用户信息 24 | is ClearUserInfo -> 25 | state.copy(userName = null, cookies = setOf(), collectIds = setOf(), collectChanges = uuid()) 26 | // 收藏文章 27 | is CollectArticle -> 28 | state.copy(collectIds = state.collectIds + action.articleId, collectChanges = uuid()) 29 | // 取消收藏文章 30 | is DisCollectArticle -> 31 | state.copy(collectIds = state.collectIds - action.articleId, collectChanges = uuid()) 32 | // 切换“自动缓存”的状态 33 | is ToggleAutoCache -> 34 | state.copy(autoCache = !state.autoCache) 35 | // 切换“无图模式”的状态 36 | is ToggleNoImage -> 37 | state.copy(noImage = !state.noImage) 38 | // 切换“夜间模式”的状态 39 | is ToggleDarkMode -> 40 | state.copy(darkMode = !state.darkMode) 41 | // 更新应用版本信息 42 | is UpdateVersion -> 43 | state.copy(version = APP_VERSION) 44 | // Default 45 | else -> 46 | state 47 | } 48 | 49 | private fun uuid(): String = UUID.randomUUID().toString() 50 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_register.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 22 | 23 | 30 | 31 | 38 | 39 | 47 | 48 | -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/hierarchy/HierarchyChildDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.hierarchy 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import app.itgungnir.kwa.common.WebActivity 7 | import app.itgungnir.kwa.support.R 8 | import kotlinx.android.synthetic.main.list_item_hierarchy_article.view.* 9 | import my.itgungnir.grouter.api.Router 10 | import my.itgungnir.ui.easy_adapter.BaseDelegate 11 | import my.itgungnir.ui.easy_adapter.EasyAdapter 12 | import my.itgungnir.ui.html 13 | import my.itgungnir.ui.onAntiShakeClick 14 | 15 | class HierarchyChildDelegate : BaseDelegate() { 16 | 17 | override fun layoutId(): Int = R.layout.list_item_hierarchy_article 18 | 19 | override fun onCreateVH(container: View) {} 20 | 21 | @SuppressLint("SetTextI18n") 22 | override fun onBindVH( 23 | item: HierarchyChildState.HierarchyArticleVO, 24 | holder: EasyAdapter.VH, 25 | position: Int, 26 | payloads: MutableList 27 | ) { 28 | 29 | holder.render(item) { 30 | 31 | this.onAntiShakeClick(2000L) { 32 | Router.instance.with(context) 33 | .target(WebActivity) 34 | .addParam("id", item.id) 35 | .addParam("originId", item.originId) 36 | .addParam("title", item.title) 37 | .addParam("url", item.link) 38 | .go() 39 | } 40 | 41 | authorView.text = "${context.getString(R.string.icon_author)} ${item.author}" 42 | 43 | titleView.text = html(item.title) 44 | 45 | if (payloads.isNotEmpty()) { 46 | val payload = payloads[0] 47 | payload.getString("PL_DATE")?.let { 48 | dateView.text = it 49 | } 50 | } else { 51 | dateView.text = item.date 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/search_result/SearchResultDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.search_result 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import app.itgungnir.kwa.common.WebActivity 7 | import app.itgungnir.kwa.support.R 8 | import kotlinx.android.synthetic.main.list_item_search_article.view.* 9 | import my.itgungnir.grouter.api.Router 10 | import my.itgungnir.ui.easy_adapter.BaseDelegate 11 | import my.itgungnir.ui.easy_adapter.EasyAdapter 12 | import my.itgungnir.ui.html 13 | import my.itgungnir.ui.onAntiShakeClick 14 | 15 | class SearchResultDelegate : BaseDelegate() { 16 | 17 | override fun layoutId(): Int = R.layout.list_item_search_article 18 | 19 | override fun onCreateVH(container: View) { 20 | } 21 | 22 | @SuppressLint("SetTextI18n") 23 | override fun onBindVH( 24 | item: SearchResultState.SearchResultArticleVO, 25 | holder: EasyAdapter.VH, 26 | position: Int, 27 | payloads: MutableList 28 | ) { 29 | 30 | holder.render(item) { 31 | 32 | this.onAntiShakeClick(2000L) { 33 | Router.instance.with(context) 34 | .target(WebActivity) 35 | .addParam("id", item.id) 36 | .addParam("originId", item.originId) 37 | .addParam("title", item.title) 38 | .addParam("url", item.link) 39 | .go() 40 | } 41 | 42 | authorView.text = "${context.getString(R.string.icon_author)} ${item.author}" 43 | 44 | titleView.text = html(item.title) 45 | 46 | if (payloads.isNotEmpty()) { 47 | val payload = payloads[0] 48 | payload.getString("PL_DATE")?.let { 49 | dateView.text = it 50 | } 51 | } else { 52 | dateView.text = item.date 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/navigation/NavigationViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.navigation 2 | 3 | import android.annotation.SuppressLint 4 | import app.itgungnir.kwa.common.http.HttpClient 5 | import app.itgungnir.kwa.common.http.handleResult 6 | import app.itgungnir.kwa.common.http.io2Main 7 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 8 | 9 | class NavigationViewModel : BaseViewModel(initialState = NavigationState()) { 10 | 11 | @SuppressLint("CheckResult") 12 | fun getNavigationList() { 13 | HttpClient.api.navigation() 14 | .handleResult() 15 | .io2Main() 16 | .subscribe({ 17 | setState { 18 | copy( 19 | tabs = it.mapIndexed { index, item -> 20 | NavigationState.NavTabVO( 21 | name = item.name, 22 | selected = index == 0 23 | ) 24 | }, 25 | items = it.map { item -> 26 | NavigationState.NavigationVO( 27 | title = item.name, 28 | children = item.articles.map { data -> 29 | NavigationState.NavigationVO.NavTagVO( 30 | id = data.id, 31 | originId = data.originId, 32 | name = data.title, 33 | link = data.link 34 | ) 35 | } 36 | ) 37 | }, 38 | error = null 39 | ) 40 | } 41 | }, { 42 | setState { 43 | copy( 44 | error = it 45 | ) 46 | } 47 | }) 48 | } 49 | } -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_setting_navigable.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 23 | 24 | 33 | 34 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_setting_digital.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 24 | 25 | 33 | 34 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/weixin/child/WeixinChildDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.weixin.child 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import app.itgungnir.kwa.common.WebActivity 7 | import app.itgungnir.kwa.main.R 8 | import kotlinx.android.synthetic.main.list_item_weixin_article.view.* 9 | import my.itgungnir.grouter.api.Router 10 | import my.itgungnir.ui.easy_adapter.BaseDelegate 11 | import my.itgungnir.ui.easy_adapter.EasyAdapter 12 | import my.itgungnir.ui.hideSoftInput 13 | import my.itgungnir.ui.html 14 | import my.itgungnir.ui.onAntiShakeClick 15 | 16 | class WeixinChildDelegate : BaseDelegate() { 17 | 18 | override fun layoutId(): Int = R.layout.list_item_weixin_article 19 | 20 | override fun onCreateVH(container: View) { 21 | } 22 | 23 | @SuppressLint("SetTextI18n") 24 | override fun onBindVH( 25 | item: WeixinChildState.WeixinArticleVO, 26 | holder: EasyAdapter.VH, 27 | position: Int, 28 | payloads: MutableList 29 | ) { 30 | 31 | holder.render(item) { 32 | 33 | this.onAntiShakeClick(2000L) { 34 | it.hideSoftInput() 35 | Router.instance.with(context) 36 | .target(WebActivity) 37 | .addParam("id", item.id) 38 | .addParam("originId", item.originId) 39 | .addParam("title", item.title) 40 | .addParam("url", item.link) 41 | .go() 42 | } 43 | 44 | authorView.text = "${context.getString(R.string.icon_author)} ${item.author}" 45 | 46 | titleView.text = html(item.title) 47 | 48 | if (payloads.isNotEmpty()) { 49 | val payload = payloads[0] 50 | payload.getString("PL_DATE")?.let { 51 | dateView.text = it 52 | } 53 | } else { 54 | dateView.text = item.date 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/project/ProjectFragment.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.project 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentStatePagerAdapter 5 | import androidx.lifecycle.Observer 6 | import app.itgungnir.kwa.common.popToast 7 | import app.itgungnir.kwa.main.R 8 | import app.itgungnir.kwa.main.project.child.ProjectChildFragment 9 | import kotlinx.android.synthetic.main.fragment_project.* 10 | import my.itgungnir.rxmvvm.core.mvvm.BaseFragment 11 | import my.itgungnir.rxmvvm.core.mvvm.buildFragmentViewModel 12 | import my.itgungnir.ui.html 13 | 14 | class ProjectFragment : BaseFragment() { 15 | 16 | private val viewModel by lazy { 17 | buildFragmentViewModel( 18 | fragment = this, 19 | viewModelClass = ProjectViewModel::class.java 20 | ) 21 | } 22 | 23 | override fun layoutId(): Int = R.layout.fragment_project 24 | 25 | override fun initComponent() { 26 | 27 | headBar.title("项目") 28 | 29 | tabLayout.setupWithViewPager(viewPager) 30 | 31 | // Init Data 32 | viewModel.getProjectTabs() 33 | } 34 | 35 | override fun observeVM() { 36 | 37 | viewModel.pick(ProjectState::tabs) 38 | .observe(this, Observer { tabs -> 39 | tabs?.a?.let { 40 | viewPager.adapter = object : FragmentStatePagerAdapter(childFragmentManager) { 41 | override fun getItem(position: Int): Fragment = 42 | ProjectChildFragment.newInstance(it[position].id) 43 | 44 | override fun getCount(): Int = 45 | it.size 46 | 47 | override fun getPageTitle(position: Int) = 48 | html(it[position].name) 49 | } 50 | } 51 | }) 52 | 53 | viewModel.pick(ProjectState::error) 54 | .observe(this, Observer { error -> 55 | error?.a?.message?.let { 56 | popToast(it) 57 | } 58 | }) 59 | } 60 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/navigation/NavigationDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.navigation 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.common.WebActivity 6 | import app.itgungnir.kwa.main.R 7 | import com.google.android.material.chip.Chip 8 | import kotlinx.android.synthetic.main.list_item_navigation_right.view.* 9 | import my.itgungnir.grouter.api.Router 10 | import my.itgungnir.ui.easy_adapter.BaseDelegate 11 | import my.itgungnir.ui.easy_adapter.EasyAdapter 12 | import my.itgungnir.ui.html 13 | import my.itgungnir.ui.onAntiShakeClick 14 | 15 | class NavigationDelegate : BaseDelegate() { 16 | 17 | override fun layoutId(): Int = R.layout.list_item_navigation_right 18 | 19 | override fun onCreateVH(container: View) { 20 | 21 | container.apply { 22 | childrenView.bind( 23 | layoutId = R.layout.list_item_tag, 24 | render = { view, data -> 25 | view.findViewById(R.id.tagView).apply { 26 | text = data.name 27 | onAntiShakeClick(2000L) { 28 | Router.instance.with(context) 29 | .target(WebActivity) 30 | .addParam("id", data.id) 31 | .addParam("originId", data.originId) 32 | .addParam("title", data.name) 33 | .addParam("url", data.link) 34 | .go() 35 | } 36 | } 37 | } 38 | ) 39 | } 40 | } 41 | 42 | override fun onBindVH( 43 | item: NavigationState.NavigationVO, 44 | holder: EasyAdapter.VH, 45 | position: Int, 46 | payloads: MutableList 47 | ) { 48 | 49 | holder.render(item) { 50 | 51 | titleView.text = html(item.title) 52 | 53 | childrenView.refresh(item.children) 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_search_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 32 | 33 | 45 | 46 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 22 | 23 | 30 | 31 | 39 | 40 | 53 | 54 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/tree/tools/ToolsDialog.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.tree.tools 2 | 3 | import androidx.lifecycle.Observer 4 | import app.itgungnir.kwa.common.WebActivity 5 | import app.itgungnir.kwa.common.popToast 6 | import app.itgungnir.kwa.main.R 7 | import com.google.android.material.chip.Chip 8 | import kotlinx.android.synthetic.main.dialog_tools.* 9 | import my.itgungnir.grouter.api.Router 10 | import my.itgungnir.rxmvvm.core.mvvm.buildFragmentViewModel 11 | import my.itgungnir.ui.dialog.FullScreenDialog 12 | import my.itgungnir.ui.onAntiShakeClick 13 | 14 | class ToolsDialog : FullScreenDialog() { 15 | 16 | private val viewModel by lazy { 17 | buildFragmentViewModel( 18 | fragment = this, 19 | viewModelClass = ToolsViewModel::class.java 20 | ) 21 | } 22 | 23 | override fun layoutId(): Int = R.layout.dialog_tools 24 | 25 | override fun initComponent() { 26 | 27 | headBar.title("常用网站") 28 | .back(getString(R.string.icon_back)) { this.dismiss() } 29 | 30 | children.bind( 31 | layoutId = R.layout.list_item_tag, 32 | render = { view, data -> 33 | view.findViewById(R.id.tagView).apply { 34 | text = data.name 35 | onAntiShakeClick(2000L) { 36 | Router.instance.with(context) 37 | .target(WebActivity) 38 | .addParam("title", data.name) 39 | .addParam("url", data.link) 40 | .go() 41 | } 42 | } 43 | } 44 | ) 45 | 46 | // Init data 47 | viewModel.getTools() 48 | } 49 | 50 | override fun observeVM() { 51 | 52 | viewModel.pick(ToolsState::items) 53 | .observe(this, Observer { items -> 54 | items?.a?.let { 55 | children.refresh(it) 56 | } 57 | }) 58 | 59 | viewModel.pick(ToolsState::error) 60 | .observe(this, Observer { error -> 61 | error?.a?.message?.let { 62 | popToast(it) 63 | } 64 | }) 65 | } 66 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/http/HttpUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.http 2 | 3 | import android.app.DownloadManager 4 | import android.content.Context 5 | import android.net.ConnectivityManager 6 | import android.net.NetworkCapabilities 7 | import android.net.Uri 8 | import android.os.Environment 9 | 10 | class HttpUtil private constructor() { 11 | 12 | companion object { 13 | val instance by lazy { HttpUtil() } 14 | } 15 | 16 | /** 17 | * 判断网络是否可用 18 | */ 19 | fun isNetworkConnected(context: Context): Boolean { 20 | val mConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 21 | val mNetworkInfo = mConnectivityManager.activeNetworkInfo 22 | if (mNetworkInfo != null) { 23 | return mNetworkInfo.isConnected 24 | } 25 | return false 26 | } 27 | 28 | /** 29 | * 判断wifi网络是否可用 30 | */ 31 | @Suppress("DEPRECATION") 32 | fun isWiFiConnected(context: Context): Boolean { 33 | val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 34 | return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 35 | val network = connectivityManager.activeNetwork 36 | val capabilities = connectivityManager.getNetworkCapabilities(network) 37 | capabilities != null && (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) 38 | } else { 39 | connectivityManager.activeNetworkInfo.type == ConnectivityManager.TYPE_WIFI 40 | } 41 | } 42 | 43 | /** 44 | * 下载APK文件 45 | */ 46 | fun downloadApk(context: Context, fileUrl: String, fileName: String) { 47 | val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager 48 | DownloadManager.Request(Uri.parse(fileUrl)).apply { 49 | setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, fileName) 50 | // 通知栏 51 | setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) 52 | setTitle("KotlinWanAndroid") 53 | setDescription("APK下载中...") 54 | setAllowedOverRoaming(false) 55 | // 入下载队列 56 | downloadManager.enqueue(this) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/delegate/BannerDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.delegate 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import android.widget.ImageView 7 | import app.itgungnir.kwa.common.WebActivity 8 | import app.itgungnir.kwa.common.load 9 | import app.itgungnir.kwa.common.redux.AppRedux 10 | import app.itgungnir.kwa.main.R 11 | import app.itgungnir.kwa.main.home.HomeState 12 | import kotlinx.android.synthetic.main.list_item_home_banner.view.* 13 | import my.itgungnir.grouter.api.Router 14 | import my.itgungnir.ui.easy_adapter.BaseDelegate 15 | import my.itgungnir.ui.easy_adapter.EasyAdapter 16 | import my.itgungnir.ui.html 17 | 18 | class BannerDelegate : BaseDelegate() { 19 | 20 | override fun layoutId(): Int = R.layout.list_item_home_banner 21 | 22 | @SuppressLint("SetTextI18n") 23 | override fun onCreateVH(container: View) { 24 | 25 | container.apply { 26 | bannerView.bind( 27 | layoutId = R.layout.list_item_home_banner_child, 28 | render = { _, view, data -> 29 | view.findViewById(R.id.imageView).apply { 30 | when (AppRedux.instance.isNoImage()) { 31 | true -> load(R.mipmap.img_placeholder) 32 | else -> load(data.url) 33 | } 34 | } 35 | }, 36 | onClick = { _, data -> 37 | Router.instance.with(context) 38 | .target(WebActivity) 39 | .addParam("title", data.title) 40 | .addParam("url", data.target) 41 | .go() 42 | }, 43 | onPageChange = { position, totalCount, data -> 44 | titleView.text = html(data.title) 45 | indexView.text = "${position + 1}/$totalCount" 46 | } 47 | ) 48 | } 49 | } 50 | 51 | override fun onBindVH( 52 | item: HomeState.BannerVO, 53 | holder: EasyAdapter.VH, 54 | position: Int, 55 | payloads: MutableList 56 | ) { 57 | 58 | holder.render(item) { 59 | bannerView.update(item.items) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/dialog_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 20 | 21 | 28 | 29 | 37 | 38 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion target_sdk_version 8 | buildToolsVersion build_tools_version 9 | defaultConfig { 10 | minSdkVersion min_sdk_version 11 | targetSdkVersion target_sdk_version 12 | } 13 | } 14 | 15 | dependencies { 16 | // Kotlin 17 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 18 | api "org.jetbrains.kotlin:kotlin-android-extensions-runtime:$kotlin_version" 19 | api "org.jetbrains.anko:anko-sdk25:$anko_version" 20 | api "org.jetbrains.anko:anko-appcompat-v7:$anko_version" 21 | // Support 22 | api "androidx.appcompat:appcompat:$appcompat_version" 23 | api "com.google.android.material:material:$material_version" 24 | api "androidx.recyclerview:recyclerview:$recyclerview_version" 25 | api "androidx.constraintlayout:constraintlayout:$constraintlayout_version" 26 | // Network 27 | api "com.squareup.okhttp3:okhttp:$okhttp_version" 28 | api "com.squareup.okhttp3:logging-interceptor:$okhttp_version" 29 | api "com.squareup.retrofit2:retrofit:$retrofit_version" 30 | api "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version" 31 | api "com.squareup.retrofit2:converter-gson:$retrofit_version" 32 | // ReactiveX 33 | api "io.reactivex.rxjava2:rxjava:$rxjava_version" 34 | api "io.reactivex.rxjava2:rxandroid:$rxandroid_version" 35 | api "com.jakewharton.rxbinding2:rxbinding:$rxbinding_version" 36 | // Glide 37 | api "com.github.bumptech.glide:glide:$glide_version" 38 | kapt "com.github.bumptech.glide:compiler:$glide_version" 39 | // Widgets 40 | api "com.google.android:flexbox:$flex_version" 41 | // ITGungnir 42 | api "com.github.ITGungnir:UIKit:$uikit_version" 43 | api "com.github.ITGungnir:RxMVVM:$rxmvvm_version" 44 | api "com.github.ITGungnir.GRouter:router_api:$router_version" 45 | api "com.github.ITGungnir:GPermission:$permission_version" 46 | // Tools 47 | api "net.danlew:android.joda:$joda_version" 48 | api "com.google.code.gson:gson:$gson_version" 49 | api "com.orhanobut:logger:$logger_version" 50 | api "androidx.multidex:multidex:$dex_version" 51 | api "com.tencent.bugly:crashreport:$bugly_sdk_version" 52 | debugApi "com.squareup.leakcanary:leakcanary-android:$leak_canary_version" 53 | debugApi "com.squareup.leakcanary:leakcanary-support-fragment:$leak_canary_version" 54 | releaseApi "com.squareup.leakcanary:leakcanary-android-no-op:$leak_canary_version" 55 | } 56 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_schedule.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 25 | 26 | 41 | 42 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/project/child/ProjectChildDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.project.child 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import app.itgungnir.kwa.common.WebActivity 7 | import app.itgungnir.kwa.common.load 8 | import app.itgungnir.kwa.common.redux.AppRedux 9 | import app.itgungnir.kwa.main.R 10 | import kotlinx.android.synthetic.main.list_item_project_article.view.* 11 | import my.itgungnir.grouter.api.Router 12 | import my.itgungnir.ui.easy_adapter.BaseDelegate 13 | import my.itgungnir.ui.easy_adapter.EasyAdapter 14 | import my.itgungnir.ui.html 15 | import my.itgungnir.ui.onAntiShakeClick 16 | 17 | class ProjectChildDelegate : BaseDelegate() { 18 | 19 | override fun layoutId(): Int = R.layout.list_item_project_article 20 | 21 | override fun onCreateVH(container: View) { 22 | } 23 | 24 | @SuppressLint("SetTextI18n") 25 | override fun onBindVH( 26 | item: ProjectChildState.ProjectArticleVO, 27 | holder: EasyAdapter.VH, 28 | position: Int, 29 | payloads: MutableList 30 | ) { 31 | 32 | holder.render(item) { 33 | 34 | this.onAntiShakeClick(2000L) { 35 | Router.instance.with(context) 36 | .target(WebActivity) 37 | .addParam("id", item.id) 38 | .addParam("originId", item.originId) 39 | .addParam("title", item.title) 40 | .addParam("url", item.link) 41 | .go() 42 | } 43 | 44 | if (AppRedux.instance.isNoImage()) { 45 | coverView.load(R.mipmap.img_placeholder) 46 | } else { 47 | coverView.load(item.cover) 48 | } 49 | 50 | authorView.text = "${context.getString(R.string.icon_author)} ${item.author}" 51 | 52 | repositoryView.onAntiShakeClick(2000L) { 53 | Router.instance.with(context) 54 | .target(WebActivity) 55 | .addParam("title", item.title) 56 | .addParam("url", item.repositoryLink) 57 | .go() 58 | } 59 | 60 | titleView.text = html(item.title) 61 | 62 | descView.text = html(item.desc) 63 | 64 | if (payloads.isNotEmpty()) { 65 | val payload = payloads[0] 66 | payload.getString("PL_DATE")?.let { 67 | dateView.text = it 68 | } 69 | } else { 70 | dateView.text = item.date 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/setting/SettingViewModel.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.setting 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import app.itgungnir.kwa.common.R 6 | import app.itgungnir.kwa.common.redux.AppRedux 7 | import app.itgungnir.kwa.common.util.CacheUtil 8 | import io.reactivex.Single 9 | import my.itgungnir.rxmvvm.core.mvvm.BaseViewModel 10 | 11 | class SettingViewModel : BaseViewModel(initialState = SettingState()) { 12 | 13 | @SuppressLint("CheckResult") 14 | fun getSettingList(context: Context) { 15 | Single.just( 16 | listOf( 17 | SettingState.DividerVO, 18 | SettingState.CheckableVO( 19 | 1, 20 | context.getString(R.string.icon_auto_cache), 21 | "自动缓存", 22 | AppRedux.instance.isAutoCache() 23 | ), 24 | SettingState.CheckableVO( 25 | 2, 26 | context.getString(R.string.icon_no_image), 27 | "无图模式", 28 | AppRedux.instance.isNoImage() 29 | ), 30 | SettingState.CheckableVO( 31 | 3, 32 | context.getString(R.string.icon_dark_mode), 33 | "夜间模式", 34 | AppRedux.instance.isDarkMode() 35 | ), 36 | SettingState.DividerVO, 37 | SettingState.DigitalVO( 38 | 4, 39 | context.getString(R.string.icon_clear_cache), 40 | "清除缓存", 41 | CacheUtil.instance.getCacheSize() 42 | ), 43 | SettingState.DividerVO, 44 | SettingState.NavigableVO(5, context.getString(R.string.icon_feedback), "意见反馈"), 45 | SettingState.NavigableVO(6, context.getString(R.string.icon_about_us), "关于我们") 46 | ) 47 | ).subscribe({ 48 | if (AppRedux.instance.isUserIn()) { 49 | setState { 50 | copy( 51 | items = it + listOf(SettingState.DividerVO, SettingState.ButtonVO), 52 | error = null 53 | ) 54 | } 55 | } else { 56 | setState { 57 | copy( 58 | items = it, 59 | error = null 60 | ) 61 | } 62 | } 63 | }, { 64 | setState { 65 | copy( 66 | error = it 67 | ) 68 | } 69 | }) 70 | } 71 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_tree.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 28 | 29 | 40 | 41 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_weixin_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 28 | 29 | 43 | 44 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_hierarchy_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 28 | 29 | 43 | 44 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app_support/src/main/res/layout/list_item_search_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 28 | 29 | 43 | 44 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/search/SearchDialog.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.search 2 | 3 | import androidx.lifecycle.Observer 4 | import app.itgungnir.kwa.common.SearchResultActivity 5 | import app.itgungnir.kwa.common.popToast 6 | import app.itgungnir.kwa.common.redux.AddSearchHistory 7 | import app.itgungnir.kwa.common.redux.AppRedux 8 | import app.itgungnir.kwa.main.R 9 | import kotlinx.android.synthetic.main.dialog_search.* 10 | import my.itgungnir.grouter.api.Router 11 | import my.itgungnir.rxmvvm.core.mvvm.buildFragmentViewModel 12 | import my.itgungnir.ui.dialog.FullScreenDialog 13 | import my.itgungnir.ui.easy_adapter.EasyAdapter 14 | import my.itgungnir.ui.easy_adapter.bind 15 | 16 | class SearchDialog : FullScreenDialog() { 17 | 18 | private var listAdapter: EasyAdapter? = null 19 | 20 | private val viewModel by lazy { 21 | buildFragmentViewModel( 22 | fragment = this, 23 | viewModelClass = SearchViewModel::class.java 24 | ) 25 | } 26 | 27 | override fun layoutId(): Int = R.layout.dialog_search 28 | 29 | override fun initComponent() { 30 | searchBar.back(getString(R.string.icon_back)) { this.dismiss() } 31 | .doOnSearch { 32 | if (it.isNotBlank()) { 33 | AppRedux.instance.dispatch(AddSearchHistory(it)) 34 | navigate(it) 35 | } 36 | } 37 | 38 | listAdapter = list.bind() 39 | .addDelegate( 40 | isForViewType = { data -> data is SearchState.SearchHotKeyVO }, 41 | delegate = SearchHotKeyDelegate { 42 | AppRedux.instance.dispatch(AddSearchHistory(it)) 43 | navigate(it) 44 | }) 45 | .addDelegate( 46 | isForViewType = { data -> data is SearchState.SearchHistoryVO }, 47 | delegate = SearchHistoryDelegate { 48 | navigate(it) 49 | }) 50 | .initialize() 51 | 52 | // Init data 53 | viewModel.initData() 54 | } 55 | 56 | override fun observeVM() { 57 | 58 | viewModel.pick(SearchState::items) 59 | .observe(this, Observer { items -> 60 | items?.a?.let { 61 | listAdapter?.update(it) 62 | } 63 | }) 64 | 65 | viewModel.pick(SearchState::error) 66 | .observe(this, Observer { error -> 67 | error?.a?.message?.let { 68 | popToast(it) 69 | } 70 | }) 71 | } 72 | 73 | private fun navigate(key: String) { 74 | Router.instance.with(this) 75 | .target(SearchResultActivity) 76 | .addParam("key", key) 77 | .go() 78 | this.dismiss() 79 | } 80 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/schedule/menu/MenuItemDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.schedule.menu 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import app.itgungnir.kwa.support.R 6 | import app.itgungnir.kwa.support.schedule.ScheduleState 7 | import kotlinx.android.synthetic.main.list_item_menu.view.* 8 | import my.itgungnir.ui.color 9 | import my.itgungnir.ui.easy_adapter.BaseDelegate 10 | import my.itgungnir.ui.easy_adapter.Differ 11 | import my.itgungnir.ui.easy_adapter.EasyAdapter 12 | import my.itgungnir.ui.easy_adapter.ListItem 13 | import org.jetbrains.anko.textColor 14 | 15 | val menuItemDiffer = object : Differ { 16 | override fun areItemsTheSame(oldItem: ListItem, newItem: ListItem): Boolean = 17 | (oldItem as ScheduleState.MenuTabVO).title == (newItem as ScheduleState.MenuTabVO).title 18 | 19 | override fun areContentsTheSame(oldItem: ListItem, newItem: ListItem): Boolean = 20 | (oldItem as ScheduleState.MenuTabVO).selected == (newItem as ScheduleState.MenuTabVO).selected 21 | 22 | override fun getChangePayload(oldItem: ListItem, newItem: ListItem): Bundle? { 23 | oldItem as ScheduleState.MenuTabVO 24 | newItem as ScheduleState.MenuTabVO 25 | val payload = Bundle() 26 | if (oldItem.selected != newItem.selected) { 27 | payload.putBoolean("PL_SELECT", newItem.selected) 28 | } 29 | return if (payload.size() == 0) null else payload 30 | } 31 | } 32 | 33 | class MenuItemDelegate( 34 | private val clickCallback: (Int, ScheduleState.MenuTabVO) -> Unit 35 | ) : BaseDelegate() { 36 | 37 | override fun layoutId(): Int = R.layout.list_item_menu 38 | 39 | override fun onCreateVH(container: View) { 40 | } 41 | 42 | override fun onBindVH( 43 | item: ScheduleState.MenuTabVO, 44 | holder: EasyAdapter.VH, 45 | position: Int, 46 | payloads: MutableList 47 | ) { 48 | 49 | holder.render(item) { 50 | 51 | title.apply { 52 | text = item.title 53 | if (payloads.isNullOrEmpty()) { 54 | textColor = when (item.selected) { 55 | true -> context.color(R.color.colorAccent) 56 | else -> context.color(R.color.text_color_level_1) 57 | } 58 | } else { 59 | val payload = payloads[0] 60 | for (key in payload.keySet()) { 61 | when (key) { 62 | "PL_SELECT" -> textColor = when (item.selected) { 63 | true -> context.color(R.color.colorAccent) 64 | else -> context.color(R.color.text_color_level_1) 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | setOnClickListener { 72 | clickCallback.invoke(holder.adapterPosition, item) 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/app/itgungnir/kwa/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.os.Bundle 6 | import android.view.Gravity 7 | import android.view.View 8 | import android.widget.ImageView 9 | import androidx.appcompat.app.AppCompatActivity 10 | import app.itgungnir.kwa.common.MainActivity 11 | import app.itgungnir.kwa.common.SplashActivity 12 | import app.itgungnir.kwa.common.http.io2Main 13 | import app.itgungnir.kwa.common.redux.AppRedux 14 | import app.itgungnir.kwa.common.redux.UpdateVersion 15 | import io.reactivex.Single 16 | import my.itgungnir.grouter.annotation.Route 17 | import my.itgungnir.grouter.api.Router 18 | import my.itgungnir.permission.GPermission 19 | import my.itgungnir.ui.color 20 | import my.itgungnir.ui.dp2px 21 | import org.jetbrains.anko.* 22 | import java.util.concurrent.TimeUnit 23 | 24 | @Route(SplashActivity) 25 | class SplashActivity : AppCompatActivity() { 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | 30 | object : AnkoComponent { 31 | override fun createView(ui: AnkoContext): View = with(ui) { 32 | verticalLayout { 33 | backgroundColor = this@SplashActivity.color(R.color.colorPure) 34 | imageView { 35 | imageResource = R.mipmap.img_placeholder 36 | scaleType = ImageView.ScaleType.CENTER_INSIDE 37 | }.lparams(ui.ctx.dp2px(140F).toInt(), ui.ctx.dp2px(100F).toInt()) { 38 | bottomMargin = ui.ctx.dp2px(50F).toInt() 39 | } 40 | }.apply { 41 | gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM 42 | } 43 | } 44 | }.setContentView(this) 45 | 46 | initComponent() 47 | } 48 | 49 | private fun initComponent() { 50 | if (!App.isFirstRun) { 51 | navigate() 52 | return 53 | } 54 | 55 | AppRedux.instance.dispatch(UpdateVersion) 56 | 57 | requestPermissions() 58 | } 59 | 60 | private fun requestPermissions() { 61 | GPermission.with(this) 62 | .onGranted { postNavigate() } 63 | .onDenied { finish() } 64 | .request( 65 | Manifest.permission.WRITE_EXTERNAL_STORAGE to "文件读写", 66 | Manifest.permission.READ_PHONE_STATE to "获取手机状态" 67 | ) 68 | } 69 | 70 | @SuppressLint("CheckResult") 71 | private fun postNavigate() { 72 | Single.timer(2L, TimeUnit.SECONDS) 73 | .io2Main() 74 | .doOnSubscribe { App.isFirstRun = false } 75 | .subscribe({ 76 | navigate() 77 | }, {}) 78 | } 79 | 80 | private fun navigate() { 81 | Router.instance.with(this) 82 | .target(MainActivity) 83 | .go() 84 | finish() 85 | } 86 | 87 | override fun onBackPressed() = Unit 88 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/register/RegisterActivity.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.register 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.lifecycle.Observer 5 | import app.itgungnir.kwa.common.RegisterActivity 6 | import app.itgungnir.kwa.common.popToast 7 | import app.itgungnir.kwa.support.R 8 | import com.jakewharton.rxbinding2.widget.RxTextView 9 | import io.reactivex.Observable 10 | import kotlinx.android.synthetic.main.activity_register.* 11 | import my.itgungnir.grouter.annotation.Route 12 | import my.itgungnir.rxmvvm.core.mvvm.BaseActivity 13 | import my.itgungnir.rxmvvm.core.mvvm.buildActivityViewModel 14 | import my.itgungnir.ui.hideSoftInput 15 | import my.itgungnir.ui.onAntiShakeClick 16 | 17 | @Route(RegisterActivity) 18 | class RegisterActivity : BaseActivity() { 19 | 20 | private val viewModel by lazy { 21 | buildActivityViewModel( 22 | activity = this, 23 | viewModelClass = RegisterViewModel::class.java 24 | ) 25 | } 26 | 27 | override fun layoutId(): Int = R.layout.activity_register 28 | 29 | @SuppressLint("CheckResult") 30 | override fun initComponent() { 31 | 32 | headBar.title("用户注册") 33 | .back(getString(R.string.icon_back)) { finish() } 34 | 35 | Observable.combineLatest( 36 | arrayOf( 37 | RxTextView.textChanges(userNameInput.getInput()), 38 | RxTextView.textChanges(passwordInput.getInput()), 39 | RxTextView.textChanges(confirmPwdInput.getInput()) 40 | ) 41 | ) { items: Array -> items.all { item -> item.toString().trim().isNotEmpty() } } 42 | .subscribe { valid: Boolean -> 43 | when (valid) { 44 | true -> 45 | register.ready("注册") 46 | else -> 47 | register.disabled("注册") 48 | } 49 | } 50 | 51 | register.apply { 52 | disabled("注册") 53 | onAntiShakeClick(2000L) { 54 | hideSoftInput() 55 | loading() 56 | val userName = userNameInput.getInput().editableText.toString().trim() 57 | val password = passwordInput.getInput().editableText.toString().trim() 58 | val confirmPwd = confirmPwdInput.getInput().editableText.toString().trim() 59 | viewModel.register(userName, password, confirmPwd) 60 | } 61 | } 62 | } 63 | 64 | override fun observeVM() { 65 | 66 | viewModel.pick(RegisterState::succeed) 67 | .observe(this, Observer { succeed -> 68 | succeed?.a?.let { 69 | register.ready("注册") 70 | finish() 71 | } 72 | }) 73 | 74 | viewModel.pick(RegisterState::error) 75 | .observe(this, Observer { error -> 76 | error?.a?.message?.let { 77 | popToast(it) 78 | register.ready("注册") 79 | } 80 | }) 81 | } 82 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/mine/MineArticleDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.mine 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import app.itgungnir.kwa.common.HierarchyActivity 7 | import app.itgungnir.kwa.common.WebActivity 8 | import app.itgungnir.kwa.main.R 9 | import app.itgungnir.kwa.main.tree.TreeState 10 | import com.google.gson.Gson 11 | import kotlinx.android.synthetic.main.list_item_mine_article.view.* 12 | import my.itgungnir.grouter.api.Router 13 | import my.itgungnir.ui.easy_adapter.BaseDelegate 14 | import my.itgungnir.ui.easy_adapter.EasyAdapter 15 | import my.itgungnir.ui.html 16 | import my.itgungnir.ui.onAntiShakeClick 17 | 18 | class MineArticleDelegate( 19 | private val onLongClick: (id: Int, originId: Int) -> Unit 20 | ) : BaseDelegate() { 21 | 22 | override fun layoutId(): Int = R.layout.list_item_mine_article 23 | 24 | override fun onCreateVH(container: View) {} 25 | 26 | @SuppressLint("SetTextI18n") 27 | override fun onBindVH( 28 | item: MineState.MineArticleVO, 29 | holder: EasyAdapter.VH, 30 | position: Int, 31 | payloads: MutableList 32 | ) { 33 | 34 | holder.render(item) { 35 | 36 | this.setOnLongClickListener { 37 | onLongClick.invoke(item.id, item.originId) 38 | true 39 | } 40 | 41 | this.onAntiShakeClick(2000L) { 42 | Router.instance.with(context) 43 | .target(WebActivity) 44 | .addParam("id", item.id) 45 | .addParam("originId", item.originId) 46 | .addParam("title", item.title) 47 | .addParam("url", item.link) 48 | .go() 49 | } 50 | 51 | authorView.text = "${context.getString(R.string.icon_author)} ${item.author}" 52 | 53 | categoryView.apply { 54 | text = item.category 55 | onAntiShakeClick(2000L) { 56 | val data = TreeState.TreeVO( 57 | name = item.category, 58 | children = listOf( 59 | TreeState.TreeVO.TreeTagVO( 60 | id = item.categoryId, 61 | name = item.category 62 | ) 63 | ) 64 | ) 65 | val json = Gson().toJson(data) 66 | Router.instance.with(context) 67 | .target(HierarchyActivity) 68 | .addParam("json", json) 69 | .go() 70 | } 71 | } 72 | 73 | titleView.text = html(item.title) 74 | 75 | if (payloads.isNotEmpty()) { 76 | val payload = payloads[0] 77 | payload.getString("PL_DATE")?.let { 78 | dateView.text = it 79 | } 80 | } else { 81 | dateView.text = item.date 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /app_support/src/main/java/app/itgungnir/kwa/support/schedule/menu/MenuTabBar.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.support.schedule.menu 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.View 6 | import androidx.constraintlayout.widget.ConstraintLayout 7 | import app.itgungnir.kwa.support.R 8 | import kotlinx.android.synthetic.main.view_schedule_menu_tab.view.* 9 | import my.itgungnir.ui.color 10 | import org.jetbrains.anko.textColor 11 | 12 | class MenuTabBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : 13 | ConstraintLayout(context, attrs, defStyleAttr) { 14 | 15 | private var isTabOpen1 = false 16 | private var isTabOpen2 = false 17 | private var isTabOpen3 = false 18 | 19 | private val textColorNormal = context.color(R.color.text_color_head_bar) 20 | private val textColorSelected = context.color(R.color.colorAccent) 21 | 22 | init { 23 | View.inflate(context, R.layout.view_schedule_menu_tab, this) 24 | } 25 | 26 | fun setClickCallback(callback1: () -> Unit, callback2: () -> Unit, callback3: () -> Unit) { 27 | tab1.setOnClickListener { 28 | isTabOpen1 = !isTabOpen1 29 | toggleArrows(0, isTabOpen1) 30 | callback1.invoke() 31 | } 32 | 33 | tab2.setOnClickListener { 34 | isTabOpen2 = !isTabOpen2 35 | toggleArrows(1, isTabOpen2) 36 | callback2.invoke() 37 | } 38 | 39 | tab3.setOnClickListener { 40 | isTabOpen3 = !isTabOpen3 41 | callback3.invoke() 42 | } 43 | } 44 | 45 | private fun toggleArrows(index: Int, isOpen: Boolean) { 46 | listOf(tabArrow1, tabArrow2).forEachIndexed { pos, arrow -> 47 | arrow.text = when (pos == index && isOpen) { 48 | true -> context.getString(R.string.icon_arrow_up) 49 | else -> context.getString(R.string.icon_arrow_down) 50 | } 51 | } 52 | } 53 | 54 | fun resetArrows(index: Int? = null) { 55 | isTabOpen1 = 0 == index 56 | isTabOpen2 = 1 == index 57 | isTabOpen3 = 2 == index 58 | tabArrow1.text = when (isTabOpen1) { 59 | true -> context.getString(R.string.icon_arrow_up) 60 | else -> context.getString(R.string.icon_arrow_down) 61 | } 62 | tabArrow2.text = when (isTabOpen2) { 63 | true -> context.getString(R.string.icon_arrow_up) 64 | else -> context.getString(R.string.icon_arrow_down) 65 | } 66 | } 67 | 68 | fun confirmTab(index: Int, selected: Boolean, title: String? = null) { 69 | when (index) { 70 | 0 -> tabText1.apply { 71 | textColor = if (selected) textColorSelected else textColorNormal 72 | text = title ?: "类型" 73 | } 74 | 1 -> tabText2.apply { 75 | textColor = if (selected) textColorSelected else textColorNormal 76 | text = title ?: "优先级" 77 | } 78 | 2 -> tab3.textColor = if (selected) textColorSelected else textColorNormal 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /common/src/main/java/app/itgungnir/kwa/common/util/CacheUtil.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.common.util 2 | 3 | import android.app.Application 4 | import java.io.File 5 | import java.math.BigDecimal 6 | 7 | class CacheUtil : Util { 8 | 9 | lateinit var cacheFile: File 10 | 11 | companion object { 12 | val instance by lazy { CacheUtil() } 13 | } 14 | 15 | override fun init(application: Application) { 16 | val filePath = "${application.cacheDir.absolutePath}${File.separator}data${File.separator}CacheFile" 17 | this.cacheFile = File(filePath) 18 | if (!cacheFile.exists()) { 19 | cacheFile.mkdirs() 20 | } 21 | } 22 | 23 | /** 24 | * 获取缓存大小 25 | */ 26 | fun getCacheSize(): String { 27 | if (!cacheFile.exists()) { 28 | cacheFile.mkdirs() 29 | } 30 | return getFormatSize(getFolderSize(cacheFile).toDouble()) 31 | } 32 | 33 | /** 34 | * 清除缓存 35 | */ 36 | fun clearCache(file: File = cacheFile): Boolean { 37 | if (file.exists() && file.isDirectory) { 38 | val children = file.list() 39 | for (aChildren in children) { 40 | val success = clearCache(File(file, aChildren)) 41 | if (!success) { 42 | return false 43 | } 44 | } 45 | } 46 | return file.delete() 47 | } 48 | 49 | private fun getFolderSize(file: File): Long { 50 | var size: Long = 0 51 | try { 52 | val fileList = file.listFiles() 53 | for (aFileList in fileList) { 54 | // 如果下面还有文件 55 | size += if (aFileList.isDirectory) { 56 | getFolderSize(aFileList) 57 | } else { 58 | aFileList.length() 59 | } 60 | } 61 | } catch (e: Exception) { 62 | e.printStackTrace() 63 | } 64 | return size 65 | } 66 | 67 | private fun getFormatSize(size: Double): String { 68 | val kiloByte = size / 1024 69 | if (kiloByte < 1) { 70 | return "0KB" 71 | } 72 | 73 | val megaByte = kiloByte / 1024 74 | if (megaByte < 1) { 75 | val result1 = BigDecimal(java.lang.Double.toString(kiloByte)) 76 | return result1.setScale(2, BigDecimal.ROUND_HALF_UP) 77 | .toPlainString() + "KB" 78 | } 79 | 80 | val gigaByte = megaByte / 1024 81 | if (gigaByte < 1) { 82 | val result2 = BigDecimal(java.lang.Double.toString(megaByte)) 83 | return result2.setScale(2, BigDecimal.ROUND_HALF_UP) 84 | .toPlainString() + "MB" 85 | } 86 | 87 | val teraBytes = gigaByte / 1024 88 | if (teraBytes < 1) { 89 | val result3 = BigDecimal(java.lang.Double.toString(gigaByte)) 90 | return result3.setScale(2, BigDecimal.ROUND_HALF_UP) 91 | .toPlainString() + "GB" 92 | } 93 | val result4 = BigDecimal(teraBytes) 94 | return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB" 95 | } 96 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/delegate/HomeArticleDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.delegate 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.View 6 | import app.itgungnir.kwa.common.HierarchyActivity 7 | import app.itgungnir.kwa.common.WebActivity 8 | import app.itgungnir.kwa.main.R 9 | import app.itgungnir.kwa.main.home.HomeState 10 | import app.itgungnir.kwa.main.tree.TreeState 11 | import com.google.gson.Gson 12 | import kotlinx.android.synthetic.main.list_item_home_article.view.* 13 | import my.itgungnir.grouter.api.Router 14 | import my.itgungnir.ui.easy_adapter.BaseDelegate 15 | import my.itgungnir.ui.easy_adapter.EasyAdapter 16 | import my.itgungnir.ui.html 17 | import my.itgungnir.ui.onAntiShakeClick 18 | 19 | class HomeArticleDelegate : BaseDelegate() { 20 | 21 | override fun layoutId(): Int = R.layout.list_item_home_article 22 | 23 | override fun onCreateVH(container: View) { 24 | } 25 | 26 | @SuppressLint("SetTextI18n") 27 | override fun onBindVH( 28 | item: HomeState.HomeArticleVO, 29 | holder: EasyAdapter.VH, 30 | position: Int, 31 | payloads: MutableList 32 | ) { 33 | 34 | holder.render(item) { 35 | 36 | this.onAntiShakeClick(2000L) { 37 | Router.instance.with(context) 38 | .target(WebActivity) 39 | .addParam("id", item.id) 40 | .addParam("originId", item.originId) 41 | .addParam("title", item.title) 42 | .addParam("url", item.link) 43 | .go() 44 | } 45 | 46 | authorView.text = "${context.getString(R.string.icon_author)} ${item.author}" 47 | 48 | categoryView.apply { 49 | text = item.category 50 | onAntiShakeClick(2000L) { 51 | val categories = item.category.split(" / ") 52 | val data = TreeState.TreeVO( 53 | name = categories[0], 54 | children = listOf( 55 | TreeState.TreeVO.TreeTagVO( 56 | id = item.categoryId, 57 | name = categories[1] 58 | ) 59 | ) 60 | ) 61 | val json = Gson().toJson(data) 62 | Router.instance.with(context) 63 | .target(HierarchyActivity) 64 | .addParam("json", json) 65 | .go() 66 | } 67 | } 68 | 69 | titleView.text = html(item.title) 70 | 71 | if (payloads.isNotEmpty()) { 72 | val payload = payloads[0] 73 | payload.getString("PL_DATE")?.let { 74 | dateView.text = it 75 | } 76 | } else { 77 | dateView.text = item.date 78 | } 79 | 80 | topView.visibility = when (item.isTop) { 81 | true -> View.VISIBLE 82 | else -> View.GONE 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/mine/add/AddArticleDialog.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.mine.add 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.lifecycle.Observer 9 | import app.itgungnir.kwa.common.popToast 10 | import app.itgungnir.kwa.main.R 11 | import com.jakewharton.rxbinding2.widget.RxTextView 12 | import io.reactivex.Observable 13 | import kotlinx.android.synthetic.main.dialog_add_article.* 14 | import my.itgungnir.rxmvvm.core.mvvm.buildFragmentViewModel 15 | import my.itgungnir.ui.dialog.NoTitleDialogFragment 16 | import my.itgungnir.ui.hideSoftInput 17 | import my.itgungnir.ui.onAntiShakeClick 18 | 19 | class AddArticleDialog : NoTitleDialogFragment() { 20 | 21 | private val viewModel by lazy { 22 | buildFragmentViewModel( 23 | fragment = this, 24 | viewModelClass = AddArticleViewModel::class.java 25 | ) 26 | } 27 | 28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = 29 | inflater.inflate(R.layout.dialog_add_article, container, false) 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | initComponent() 34 | observeVM() 35 | } 36 | 37 | @SuppressLint("CheckResult") 38 | private fun initComponent() { 39 | 40 | headBar.title("新增站外文章") 41 | .back(getString(R.string.icon_back)) { this.dismiss() } 42 | 43 | Observable.combineLatest( 44 | arrayOf( 45 | RxTextView.textChanges(titleInput.getInput()), 46 | RxTextView.textChanges(linkInput.getInput()) 47 | ) 48 | ) { items: Array -> items.all { item -> item.toString().trim().isNotEmpty() } } 49 | .subscribe { valid: Boolean -> 50 | when (valid) { 51 | true -> 52 | addButton.ready("确定新增") 53 | else -> 54 | addButton.disabled("确定新增") 55 | } 56 | } 57 | 58 | addButton.apply { 59 | disabled("确定新增") 60 | onAntiShakeClick(2000L) { 61 | loading() 62 | it.hideSoftInput() 63 | val titleStr = titleInput.getInput().editableText.toString().trim() 64 | val linkStr = linkInput.getInput().editableText.toString().trim() 65 | viewModel.addArticle(titleStr, linkStr) 66 | } 67 | } 68 | } 69 | 70 | private fun observeVM() { 71 | 72 | viewModel.pick(AddArticleState::succeed) 73 | .observe(this, Observer { succeed -> 74 | succeed?.a?.let { 75 | addButton.ready("确定新增") 76 | this.dismiss() 77 | } 78 | }) 79 | 80 | viewModel.pick(AddArticleState::error) 81 | .observe(this, Observer { error -> 82 | error?.a?.message?.let { 83 | addButton.ready("确定新增") 84 | popToast(it) 85 | } 86 | }) 87 | } 88 | } -------------------------------------------------------------------------------- /app_main/src/main/java/app/itgungnir/kwa/main/home/search/SearchHistoryDelegate.kt: -------------------------------------------------------------------------------- 1 | package app.itgungnir.kwa.main.home.search 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.widget.TextView 6 | import app.itgungnir.kwa.common.redux.AppRedux 7 | import app.itgungnir.kwa.common.redux.ClearSearchHistory 8 | import app.itgungnir.kwa.main.R 9 | import kotlinx.android.synthetic.main.list_item_search_history.view.* 10 | import my.itgungnir.ui.color 11 | import my.itgungnir.ui.easy_adapter.BaseDelegate 12 | import my.itgungnir.ui.easy_adapter.EasyAdapter 13 | import my.itgungnir.ui.flex.FlexView 14 | import my.itgungnir.ui.onAntiShakeClick 15 | import my.itgungnir.ui.status_view.StatusView 16 | import org.jetbrains.anko.textColor 17 | 18 | class SearchHistoryDelegate( 19 | private val keyClickCallback: (String) -> Unit 20 | ) : BaseDelegate() { 21 | 22 | override fun layoutId(): Int = R.layout.list_item_search_history 23 | 24 | override fun onCreateVH(container: View) { 25 | container.apply { 26 | // Clear Button 27 | clearView.onAntiShakeClick(2000L) { 28 | AppRedux.instance.dispatch(ClearSearchHistory) 29 | statusView.empty { } 30 | clearView.apply { 31 | isEnabled = false 32 | textColor = context.color(R.color.clr_divider) 33 | } 34 | } 35 | // Status View 36 | statusView.addDelegate(StatusView.Status.SUCCEED, R.layout.view_status_flex) { 37 | it.findViewById(R.id.children).bind( 38 | layoutId = R.layout.list_item_tag, 39 | render = { view, data -> 40 | view.findViewById(R.id.tagView).apply { 41 | text = data.name 42 | textColor = context.color(R.color.text_color_level_1) 43 | onAntiShakeClick(2000L) { 44 | keyClickCallback.invoke(data.name) 45 | } 46 | } 47 | } 48 | ) 49 | }.addDelegate(StatusView.Status.EMPTY, R.layout.view_status_flex_empty) { 50 | it.findViewById(R.id.tip).text = "快来搜点干货吧( •̀ ω •́ )✧" 51 | } 52 | } 53 | } 54 | 55 | override fun onBindVH( 56 | item: SearchState.SearchHistoryVO, 57 | holder: EasyAdapter.VH, 58 | position: Int, 59 | payloads: MutableList 60 | ) { 61 | 62 | holder.render(item) { 63 | 64 | if (item.data.isEmpty()) { 65 | statusView.empty { } 66 | clearView.apply { 67 | isEnabled = false 68 | textColor = context.color(R.color.clr_divider) 69 | } 70 | } else { 71 | statusView.succeed { 72 | (it as FlexView).refresh(item.data) 73 | } 74 | clearView.apply { 75 | isEnabled = true 76 | textColor = context.color(R.color.colorAccent) 77 | } 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /app_main/src/main/res/layout/list_item_mine_article.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 28 | 29 | 39 | 40 | 54 | 55 | 68 | 69 | 70 | --------------------------------------------------------------------------------