├── .gitignore ├── .idea ├── compiler.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── personal.text ├── proguard-rules.pro ├── release │ └── output-metadata.json ├── schemas │ └── com.yollpoll.nmb.db.MainDB │ │ └── 1.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── nmb │ │ └── ExampleInstrumentedTest.kt │ ├── cpp │ ├── includes │ │ └── native-lib.h │ └── native-lib.cpp │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ └── nmb.cpp │ ├── java │ │ └── com │ │ │ └── yollpoll │ │ │ └── nmb │ │ │ ├── AboutProject.kt │ │ │ ├── App.kt │ │ │ ├── MyCrashHandler.kt │ │ │ ├── adapter │ │ │ ├── ExampleLoadStateAdapter.kt │ │ │ ├── ImagePagerAdapter.kt │ │ │ └── ThreadAdapter.kt │ │ │ ├── db │ │ │ ├── ArticleDao.kt │ │ │ ├── BaseDao.kt │ │ │ ├── CookieDao.kt │ │ │ ├── DraftDao.kt │ │ │ ├── ForumDao.kt │ │ │ ├── HistoryDao.kt │ │ │ ├── MainDB.kt │ │ │ └── MySpeechDao.kt │ │ │ ├── di │ │ │ ├── AppWidgetDI.kt │ │ │ ├── ArgumentsDI.kt │ │ │ ├── ArticleDetailViewModelDI.kt │ │ │ ├── CommonDi.kt │ │ │ ├── DBModuleDI.kt │ │ │ ├── DiAnnotation.kt │ │ │ ├── HomeViewModelDI.kt │ │ │ ├── LauncherViewModelDI.kt │ │ │ ├── NetModuleDI.kt │ │ │ └── RepositoryDI.kt │ │ │ ├── model │ │ │ ├── bean │ │ │ │ ├── Announcement.kt │ │ │ │ ├── Article.kt │ │ │ │ ├── CookieBean.kt │ │ │ │ ├── DraftBean.kt │ │ │ │ ├── Fourm.kt │ │ │ │ ├── HistoryBean.kt │ │ │ │ ├── MySpeechBean.kt │ │ │ │ └── SettingBean.kt │ │ │ └── repository │ │ │ │ ├── ArticleDetailRepository.kt │ │ │ │ ├── CookieRepository.kt │ │ │ │ ├── DraftRepository.kt │ │ │ │ ├── ForumRepository.kt │ │ │ │ ├── HomeRepository.kt │ │ │ │ ├── IRepository.kt │ │ │ │ ├── LauncherRepository.kt │ │ │ │ └── UserRepository.kt │ │ │ ├── net │ │ │ ├── Cookie.kt │ │ │ ├── CoverImgInterceptor.kt │ │ │ ├── HttpService.kt │ │ │ ├── HttpServiceFactory.kt │ │ │ ├── LoggerInterceptor.kt │ │ │ ├── NMBInterceptor.kt │ │ │ ├── Url.kt │ │ │ └── Utils.kt │ │ │ ├── router │ │ │ ├── Dispatch.kt │ │ │ └── Route.kt │ │ │ ├── schedule │ │ │ └── UpdateAppWidgetWork.kt │ │ │ ├── service │ │ │ ├── ThreadReplyService.kt │ │ │ └── UpdateThreadWidgetService.kt │ │ │ └── view │ │ │ ├── activity │ │ │ ├── AuthorActivity.kt │ │ │ ├── CollectionActivity.kt │ │ │ ├── CookieActivity.kt │ │ │ ├── DraftActivity.kt │ │ │ ├── DrawingActivity.kt │ │ │ ├── DrawingActivity2.java │ │ │ ├── ForumSettingActivity.kt │ │ │ ├── HistoryActivity.kt │ │ │ ├── HomeActivity.kt │ │ │ ├── ImageActivity.kt │ │ │ ├── LauncherActivity.kt │ │ │ ├── MySpeechActivity.kt │ │ │ ├── NewThreadActivity.kt │ │ │ ├── QRActivity.kt │ │ │ ├── SettingActivity.kt │ │ │ ├── ShieldActivity.kt │ │ │ ├── ThreadDetailActivity.kt │ │ │ ├── ThreadImageActivity.kt │ │ │ └── WebActivity.kt │ │ │ └── widgets │ │ │ ├── BindAdapter.kt │ │ │ ├── ChangeBurshWidthView.java │ │ │ ├── ChangeLineViewGroup.java │ │ │ ├── DrawView.java │ │ │ ├── ImportCollectionDialog.kt │ │ │ ├── InputDialog.kt │ │ │ ├── LinkArticleDialog.kt │ │ │ ├── MyFAB.kt │ │ │ ├── MyFloatingMenu.kt │ │ │ ├── MyImageView.kt │ │ │ ├── SelectColorDialog.kt │ │ │ ├── SelectPageDialog.kt │ │ │ ├── ThreadMenuDialog.kt │ │ │ ├── ThreadWidget.kt │ │ │ ├── Utils.kt │ │ │ ├── VSensorLayout.kt │ │ │ ├── emoji │ │ │ ├── ChooseEmojiDialogFragment.kt │ │ │ ├── EmojiUtils.kt │ │ │ ├── PicEmojiAdapter.kt │ │ │ └── WordEmojiAdapter.kt │ │ │ └── tag │ │ │ └── ChooseTagActivity.kt │ └── res │ │ ├── anim │ │ ├── new_thread_anim.xml │ │ ├── new_thread_anim_close.xml │ │ └── new_thread_layout_anim.xml │ │ ├── drawable-night │ │ └── ic_checked.xml │ │ ├── drawable-v24 │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_logo.png │ │ └── shape_new_thread_tag.xml │ │ ├── drawable │ │ ├── ic_add_cookie.xml │ │ ├── ic_checked.xml │ │ ├── ic_launcher_background.xml │ │ ├── ripple_forum.xml │ │ ├── ripple_forum_hide.xml │ │ ├── ripple_item.xml │ │ ├── ripple_widget.xml │ │ ├── shape_cookie_color.xml │ │ ├── shape_drawer.xml │ │ ├── shape_line.xml │ │ ├── shape_right_drawer.xml │ │ └── shape_tag.xml │ │ ├── layout │ │ ├── activity_author.xml │ │ ├── activity_choose_tag.xml │ │ ├── activity_collection.xml │ │ ├── activity_cookie.xml │ │ ├── activity_draft.xml │ │ ├── activity_drawing.xml │ │ ├── activity_forum_setting.xml │ │ ├── activity_history.xml │ │ ├── activity_home.xml │ │ ├── activity_image.xml │ │ ├── activity_main.xml │ │ ├── activity_my_speech.xml │ │ ├── activity_newthread.xml │ │ ├── activity_qr.xml │ │ ├── activity_setting.xml │ │ ├── activity_shield.xml │ │ ├── activity_thread_detail.xml │ │ ├── activity_thread_image.xml │ │ ├── activity_web.xml │ │ ├── alert_choose_photo.xml │ │ ├── dialog_brush_width.xml │ │ ├── dialog_choose_color.xml │ │ ├── dialog_choose_emoji.xml │ │ ├── dialog_import_collection.xml │ │ ├── dialog_input.xml │ │ ├── dialog_select_color.xml │ │ ├── dialog_select_page.xml │ │ ├── dialog_thread_menu.xml │ │ ├── fragment_image.xml │ │ ├── include_drawer.xml │ │ ├── include_title.xml │ │ ├── item_cookie.xml │ │ ├── item_draft.xml │ │ ├── item_for_custom_spinner.xml │ │ ├── item_forum.xml │ │ ├── item_history.xml │ │ ├── item_pic_emoji.xml │ │ ├── item_setting_forum.xml │ │ ├── item_speech.xml │ │ ├── item_tag.xml │ │ ├── item_thread.xml │ │ ├── item_word_emoji.xml │ │ ├── load_state_item.xml │ │ └── widget_thread.xml │ │ ├── menu │ │ ├── menu_article_detail.xml │ │ ├── menu_collection.xml │ │ ├── menu_cookie.xml │ │ ├── menu_drawer.xml │ │ ├── menu_history.xml │ │ ├── menu_home.xml │ │ ├── menu_img.xml │ │ ├── menu_new_thread.xml │ │ └── menu_web.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_round.xml │ │ ├── icon_drawer.png │ │ ├── icon_emoji.png │ │ ├── icon_pic.png │ │ └── icon_send.png │ │ ├── mipmap-mdpi │ │ ├── ic_send.png │ │ ├── icon_show.png │ │ ├── lwn_1.png │ │ ├── lwn_10.png │ │ ├── lwn_100.png │ │ ├── lwn_101.png │ │ ├── lwn_102.png │ │ ├── lwn_103.png │ │ ├── lwn_104.png │ │ ├── lwn_105.png │ │ ├── lwn_106.png │ │ ├── lwn_11.png │ │ ├── lwn_12.png │ │ ├── lwn_13.png │ │ ├── lwn_14.png │ │ ├── lwn_15.png │ │ ├── lwn_16.png │ │ ├── lwn_17.png │ │ ├── lwn_18.png │ │ ├── lwn_19.png │ │ ├── lwn_2.png │ │ ├── lwn_20.png │ │ ├── lwn_21.png │ │ ├── lwn_22.png │ │ ├── lwn_23.png │ │ ├── lwn_24.png │ │ ├── lwn_25.png │ │ ├── lwn_26.png │ │ ├── lwn_27.png │ │ ├── lwn_28.png │ │ ├── lwn_29.png │ │ ├── lwn_3.png │ │ ├── lwn_30.png │ │ ├── lwn_31.png │ │ ├── lwn_32.png │ │ ├── lwn_33.png │ │ ├── lwn_34.png │ │ ├── lwn_35.png │ │ ├── lwn_36.png │ │ ├── lwn_37.png │ │ ├── lwn_38.png │ │ ├── lwn_39.png │ │ ├── lwn_4.png │ │ ├── lwn_40.png │ │ ├── lwn_41.png │ │ ├── lwn_42.png │ │ ├── lwn_43.png │ │ ├── lwn_44.png │ │ ├── lwn_45.png │ │ ├── lwn_46.png │ │ ├── lwn_47.png │ │ ├── lwn_48.png │ │ ├── lwn_49.png │ │ ├── lwn_5.png │ │ ├── lwn_50.png │ │ ├── lwn_51.png │ │ ├── lwn_52.png │ │ ├── lwn_53.png │ │ ├── lwn_54.png │ │ ├── lwn_55.png │ │ ├── lwn_56.png │ │ ├── lwn_57.png │ │ ├── lwn_58.png │ │ ├── lwn_59.png │ │ ├── lwn_6.png │ │ ├── lwn_60.png │ │ ├── lwn_61.png │ │ ├── lwn_62.png │ │ ├── lwn_63.png │ │ ├── lwn_64.png │ │ ├── lwn_65.png │ │ ├── lwn_66.png │ │ ├── lwn_67.png │ │ ├── lwn_68.png │ │ ├── lwn_69.png │ │ ├── lwn_7.png │ │ ├── lwn_70.png │ │ ├── lwn_71.png │ │ ├── lwn_72.png │ │ ├── lwn_73.png │ │ ├── lwn_74.png │ │ ├── lwn_75.png │ │ ├── lwn_76.png │ │ ├── lwn_77.png │ │ ├── lwn_78.png │ │ ├── lwn_79.png │ │ ├── lwn_8.png │ │ ├── lwn_80.png │ │ ├── lwn_81.png │ │ ├── lwn_82.png │ │ ├── lwn_83.png │ │ ├── lwn_84.png │ │ ├── lwn_85.png │ │ ├── lwn_86.png │ │ ├── lwn_87.png │ │ ├── lwn_88.png │ │ ├── lwn_89.png │ │ ├── lwn_9.png │ │ ├── lwn_90.png │ │ ├── lwn_91.png │ │ ├── lwn_92.png │ │ ├── lwn_93.png │ │ ├── lwn_94.png │ │ ├── lwn_95.png │ │ ├── lwn_96.png │ │ ├── lwn_97.png │ │ ├── lwn_98.png │ │ └── lwn_99.png │ │ ├── mipmap-night-xxxhdpi │ │ ├── ic_action.png │ │ └── ic_github.png │ │ ├── mipmap-xhdpi │ │ ├── icon_cleaner.png │ │ ├── icon_cleaner_fill.png │ │ ├── icon_clear.png │ │ ├── icon_draw_color.png │ │ ├── icon_draw_menu.png │ │ └── icon_draw_width.png │ │ ├── mipmap-xxxhdpi │ │ ├── app_widget_pic.png │ │ ├── bg_cover.png │ │ ├── ic_ac.png │ │ ├── ic_action.png │ │ ├── ic_cookie.png │ │ ├── ic_donate.png │ │ ├── ic_download.png │ │ ├── ic_drawer.png │ │ ├── ic_emoji.png │ │ ├── ic_full_screen.png │ │ ├── ic_full_screen_selected.png │ │ ├── ic_github.png │ │ ├── ic_logo.png │ │ └── ic_pic.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimen.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── app_widgets_thread.xml │ │ ├── provider_paths.xml │ │ └── shortcuts.xml │ └── test │ └── java │ └── com │ └── yollpoll │ └── nmb │ └── ExampleUnitTest.kt ├── base ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── base │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yollpoll │ │ │ ├── base │ │ │ ├── AnnouncementDialog.kt │ │ │ ├── BaseAdapter.kt │ │ │ ├── BaseDialogFragment.kt │ │ │ ├── BasePagingSource.kt │ │ │ ├── CommonDialog.kt │ │ │ ├── Constant.kt │ │ │ ├── Extension.kt │ │ │ ├── NMBActivity.kt │ │ │ ├── NMBApplication.kt │ │ │ ├── NMBDialog.java │ │ │ └── NmbBaseAdapter.kt │ │ │ ├── extensions │ │ │ └── context.kt │ │ │ ├── floweventbus │ │ │ ├── BusInterceptor.kt │ │ │ └── FlowEventBus.kt │ │ │ ├── skin │ │ │ ├── Skin.kt │ │ │ ├── SkinConfig.kt │ │ │ ├── SkinHandler.kt │ │ │ └── SkinInflaterFactory.kt │ │ │ └── utils │ │ │ ├── BitmapUtils.kt │ │ │ ├── ClickSpanMovementMethod.kt │ │ │ ├── ImageDownloader.kt │ │ │ ├── ImageUtils.kt │ │ │ ├── MyClickableSpan.kt │ │ │ ├── TransFormContent.kt │ │ │ └── Utils.kt │ └── res │ │ ├── animator │ │ └── material_item_animator.xml │ │ ├── drawable-night │ │ └── shape_material_item.xml │ │ ├── drawable │ │ ├── shape_dialog.xml │ │ └── shape_material_item.xml │ │ ├── layout │ │ ├── dialog_common.xml │ │ └── dialog_layout.xml │ │ ├── mipmap │ │ └── bg_cover.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimen.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── yollpoll │ └── base │ └── ExampleUnitTest.kt ├── build-framework.gradle ├── build.gradle ├── business ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── business │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yollpoll │ │ │ └── business │ │ │ └── TestActivity.kt │ └── res │ │ ├── layout │ │ └── activity_home.xml │ │ └── values │ │ ├── colors.xml │ │ └── dimen.xml │ └── test │ └── java │ └── com │ └── yollpoll │ └── business │ └── ExampleUnitTest.kt ├── config.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ilog ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── ilog │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── yollpoll │ │ └── ilog │ │ └── Ilog.kt │ └── test │ └── java │ └── com │ └── yollpoll │ └── ilog │ └── ExampleUnitTest.kt ├── iqr ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── nmb │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── yollpoll │ │ └── nmb │ │ └── Iqr.kt │ └── test │ └── java │ └── com │ └── yollpoll │ └── nmb │ └── ExampleUnitTest.kt ├── log ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── log │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── yollpoll │ │ └── log │ │ ├── CloseUtils.java │ │ ├── FileUtils.java │ │ ├── FileWriter.java │ │ ├── ILogImpl.kt │ │ ├── LogReceiver.java │ │ ├── LogService.java │ │ ├── LogTools.java │ │ ├── LogWorker.kt │ │ └── MKFileIOUtils.java │ └── test │ └── java │ └── com │ └── yollpoll │ └── log │ └── ExampleUnitTest.kt ├── qrlib ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── yollpoll │ │ └── qrlib │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── yollpoll │ │ │ └── qrlib │ │ │ ├── QRCodeActivity.kt │ │ │ ├── QrCodeAnalyzer.kt │ │ │ ├── QrUtils.kt │ │ │ └── ScanView.kt │ └── res │ │ ├── drawable │ │ ├── ic_photo.xml │ │ ├── icon_focus.png │ │ └── scan_light.png │ │ └── layout │ │ └── activity_qr_code.xml │ └── test │ └── java │ └── com │ └── yollpoll │ └── qrlib │ └── ExampleUnitTest.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | /.idea/ 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | X岛Android客户端 2 | 语言: kotlin 3 | 最低系统版本: android 5.0 4 | 采用最新Material You,根据系统定制app配色,快去修改系统颜色试试吧。 5 | 操作说明:首页右下角按钮分别支持左滑(弹出板块选择)、右滑(弹出设置)、上滑(回到列表顶部)、下滑(刷新)、点击(发新串) 6 | 后续计划:准备搞一个跑团功能,我观察到跑团比较容易出现各种分支和各说各的,所以准备加入树状图和强制收缩时间线的功能,目前还没有什么头绪。 7 | 8 | 技术说明: 9 | 整体项目采用MVVM+APT动态代码生成+组件化+依赖注入。 10 | 其中基础架构全部由我自己实现,参考主页中的其他项目(已发布MavenCenter,接入方式具体看wiki): 11 | 基础架构:https://github.com/yollpoll/framework/wiki/%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95 12 | 路由组建:https://github.com/yollpoll/sRouter 13 | 依赖注入采用hilt。 14 | 出于练手的目的,项目大量采用jetpack库内容,欢迎交流学习,这年头还在深入学习android的不多了好寂寞 15 | 16 | 联动: 17 | 好多年前写的A岛客户端(肥宅岛)(停止维护): 18 | https://github.com/yollpoll/MyApp 19 | 另有一个采用compose开发的客户端(鸽中): 20 | https://github.com/yollpoll/nmbAndroidCompose 21 | 另有一个electron的桌面客户端(鸽中): 22 | https://github.com/yollpoll/nmb_electron -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -ignorewarnings 23 | -keepattributes *Annotation* 24 | -keepattributes Exceptions 25 | -keepattributes InnerClasses 26 | -keepattributes Signature 27 | -keepattributes SourceFile,LineNumberTable 28 | -keep class com.hianalytics.android.**{*;} 29 | -keep class com.huawei.**{*;} 30 | -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.yollpoll.nmb", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "versionCode": 1, 14 | "versionName": "0.0.1", 15 | "outputFile": "app-release.apk" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /app/schemas/com.yollpoll.nmb.db.MainDB/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "e66a989af2ab557f8fd42a97ad5c6ce2", 6 | "entities": [ 7 | { 8 | "tableName": "CookieBean", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`cookie` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`name`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "cookie", 13 | "columnName": "cookie", 14 | "affinity": "TEXT", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | } 23 | ], 24 | "primaryKey": { 25 | "columnNames": [ 26 | "name" 27 | ], 28 | "autoGenerate": false 29 | }, 30 | "indices": [], 31 | "foreignKeys": [] 32 | } 33 | ], 34 | "views": [], 35 | "setupQueries": [ 36 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 37 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e66a989af2ab557f8fd42a97ad5c6ce2')" 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/yollpoll/nmb/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.yollpoll.nmb", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/cpp/includes/native-lib.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 宋鹏祺 on 2023/1/5. 3 | // 4 | 5 | #ifndef NMB_NATIVE_LIB_H 6 | #define NMB_NATIVE_LIB_H 7 | 8 | #endif //NMB_NATIVE_LIB_H 9 | -------------------------------------------------------------------------------- /app/src/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by 宋鹏祺 on 2023/1/5. 3 | // 4 | 5 | #include "native-lib.h" 6 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # For more information about using CMake with Android Studio, read the 3 | # documentation: https://d.android.com/studio/projects/add-native-code.html 4 | 5 | # Sets the minimum version of CMake required to build the native library. 6 | 7 | cmake_minimum_required(VERSION 3.18.1) 8 | 9 | # Declares and names the project. 10 | 11 | project("nmb") 12 | 13 | # Creates and names a library, sets it as either STATIC 14 | # or SHARED, and provides the relative paths to its source code. 15 | # You can define multiple libraries, and CMake builds them for you. 16 | # Gradle automatically packages shared libraries with your APK. 17 | 18 | add_library( # Sets the name of the library. 19 | nmb 20 | 21 | # Sets the library as a shared library. 22 | SHARED 23 | 24 | # Provides a relative path to your source file(s). 25 | nmb.cpp ) 26 | 27 | # Searches for a specified prebuilt library and stores the path as a 28 | # variable. Because CMake includes system libraries in the search path by 29 | # default, you only need to specify the name of the public NDK library 30 | # you want to add. CMake verifies that the library exists before 31 | # completing its build. 32 | 33 | find_library( # Sets the name of the path variable. 34 | log-lib 35 | 36 | # Specifies the name of the NDK library that 37 | # you want CMake to locate. 38 | log ) 39 | 40 | # Specifies libraries CMake should link to your target library. You 41 | # can link multiple libraries, such as libraries you define in this 42 | # build script, prebuilt third-party libraries, or system libraries. 43 | 44 | target_link_libraries( # Specifies the target library. 45 | nmb 46 | 47 | # Links the target library to the log library 48 | # included in the NDK. 49 | ${log-lib} ) 50 | -------------------------------------------------------------------------------- /app/src/main/cpp/nmb.cpp: -------------------------------------------------------------------------------- 1 | // Write C++ code here. 2 | // 3 | // Do not forget to dynamically load the C++ library into your application. 4 | // 5 | // For instance, 6 | // 7 | // In MainActivity.java: 8 | // static { 9 | // System.loadLibrary("nmb"); 10 | // } 11 | // 12 | // Or, in MainActivity.kt: 13 | // companion object { 14 | // init { 15 | // System.loadLibrary("nmb") 16 | // } 17 | // } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/AboutProject.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb 2 | 3 | /** 4 | * Created by spq on 2022/11/9 5 | */ 6 | const val aboutProject="简介\n" + 7 | "语言: kotlin\n" + 8 | "最低系统版本: android 5.0\n" + 9 | "采用最新Material You,根据系统定制app配色,快去修改系统颜色试试吧。\n" + 10 | "操作说明:首页右下角按钮分别支持左滑(弹出板块选择)、右滑(弹出设置)、上滑(回到列表顶部)、下滑(刷新)、点击(发新串)、\n" + 11 | "\n" + 12 | "技术说明:\n" + 13 | "整体项目采用MVVM+APT动态代码生成+组件化+依赖注入。\n" + 14 | "其中基础架构全部由我自己实现,参考主页中的其他项目(已发布MavenCenter,接入方式具体看wiki):\n" + 15 | "基础架构:https://github.com/yollpoll/framework/wiki/%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95\n" + 16 | "路由组建:https://github.com/yollpoll/sRouter\n" + 17 | "依赖注入采用hilt。\n" + 18 | "出于练手的目的,项目大量采用jetpack库内容,欢迎交流学习,这年头还在深入学习android的不多了好寂寞\n" + 19 | "\n" + 20 | "联动:\n" + 21 | "好多年前写的A岛客户端(肥宅岛)(停止维护):\n" + 22 | "https://github.com/yollpoll/MyApp\n" + 23 | "另有一个采用compose开发的客户端(鸽中):\n" + 24 | "https://github.com/yollpoll/nmbAndroidCompose\n" + 25 | "另有一个electron的桌面客户端(鸽中):\n" + 26 | "https://github.com/yollpoll/nmb_electron" -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/db/BaseDao.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.db 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | interface BaseDao { 7 | @Insert(onConflict = OnConflictStrategy.REPLACE) 8 | fun insertAll(list: List) 9 | 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | suspend fun insertOne(element: T) 12 | 13 | @Delete 14 | fun delete(element: T) 15 | 16 | @Delete 17 | fun deleteList(elements: List) 18 | 19 | @Delete 20 | fun deleteSome(vararg elements: T) 21 | 22 | @Update 23 | fun update(element: T) 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/db/CookieDao.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | import com.yollpoll.nmb.model.bean.CookieBean 6 | 7 | @Dao 8 | interface CookieDao : BaseDao { 9 | @Query("SELECT * FROM COOKIEBEAN ORDER BY used DESC") 10 | suspend fun queryAll(): List 11 | 12 | @Query("SELECT * FROM COOKIEBEAN WHERE USED LIKE 1 ") 13 | suspend fun queryUsed(): CookieBean? 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/db/DraftDao.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.db 2 | 3 | import androidx.room.* 4 | import com.yollpoll.nmb.model.bean.DraftBean 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by spq on 2022/12/6 9 | */ 10 | @Dao 11 | interface DraftDao { 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun insertAll(list: List) 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | suspend fun insert(vararg bean: DraftBean) 17 | 18 | @Delete 19 | suspend fun delete(element: DraftBean) 20 | 21 | @Delete 22 | suspend fun deleteList(elements: List) 23 | 24 | @Delete 25 | suspend fun deleteSome(vararg elements: DraftBean) 26 | 27 | @Update 28 | suspend fun update(vararg element: DraftBean) 29 | 30 | @Query("SELECT * FROM draft ORDER BY update_time DESC") 31 | suspend fun query(): List 32 | 33 | @Query("SELECT * FROM draft ORDER BY update_time DESC") 34 | fun queryFlow(): Flow> 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/db/ForumDao.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.db 2 | 3 | import androidx.room.* 4 | import com.yollpoll.nmb.model.bean.ArticleItem 5 | import com.yollpoll.nmb.model.bean.ForumDetail 6 | 7 | /** 8 | * Created by spq on 2022/12/2 9 | */ 10 | @Dao 11 | interface ForumDao { 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun insertAll(forumList: List): List 14 | 15 | @Insert(onConflict = OnConflictStrategy.IGNORE) 16 | suspend fun insertIfUnExist(forumList: List) 17 | 18 | @Update 19 | suspend fun update(vararg forumDetail: ForumDetail) 20 | 21 | @Query("SELECT * FROM forum ORDER BY sort") 22 | suspend fun queryAll(): List 23 | 24 | @Query("SELECT * FROM forum WHERE show=1 ORDER BY sort") 25 | suspend fun queryShow(): List 26 | 27 | @Query("SELECT * FROM forum WHERE id like :id") 28 | suspend fun queryForum(id: String): List 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/db/HistoryDao.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.db 2 | 3 | import androidx.room.* 4 | import com.yollpoll.nmb.model.bean.ArticleItem 5 | import com.yollpoll.nmb.model.bean.HistoryBean 6 | import com.yollpoll.nmb.model.bean.MySpeechBean 7 | import retrofit2.http.DELETE 8 | 9 | /** 10 | * Created by spq on 2022/11/11 11 | */ 12 | @Dao 13 | interface HistoryDao { 14 | @Insert(onConflict = OnConflictStrategy.REPLACE) 15 | suspend fun insertAll( mySpeechBean: List) 16 | 17 | @Query("DELETE FROM historybean") 18 | suspend fun clearAll() 19 | 20 | @Query("SELECT * FROM historybean ORDER BY update_time DESC") 21 | suspend fun query(): List 22 | 23 | @Delete 24 | suspend fun delete(bean:HistoryBean) 25 | 26 | // @Query("SELECT * FROM myspeechbean") 27 | // suspend fun query(): List 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/db/MySpeechDao.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.db 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import com.yollpoll.nmb.model.bean.MySpeechBean 9 | 10 | @Dao 11 | interface MySpeechDao { 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun insertAll(mySpeechBean: List) 14 | 15 | @Query("DELETE FROM myspeechbean") 16 | suspend fun clearAll() 17 | 18 | @Query("SELECT * FROM myspeechbean ORDER BY id DESC") 19 | suspend fun query(): List 20 | 21 | // @Query("SELECT * FROM myspeechbean") 22 | // suspend fun query(): List 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/AppWidgetDI.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.framework.net.http.RetrofitFactory 4 | import com.yollpoll.nmb.model.repository.HomeRepository 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @InstallIn(SingletonComponent::class)//生命周期 12 | @Module 13 | class AppWidgetDI { 14 | @AppWidgetRepository 15 | @Provides 16 | @Singleton//作用域 17 | fun provideRepository(@CommonRetrofitFactory retrofitFactory: RetrofitFactory): HomeRepository = 18 | HomeRepository(retrofitFactory) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/ArgumentsDI.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.yollpoll.nmb.di 10 | 11 | import dagger.Module 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.android.components.ViewModelComponent 14 | 15 | abstract class ArgumentsDI { 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/ArticleDetailViewModelDI.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. 4 | * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. 5 | * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. 6 | * Vestibulum commodo. Ut rhoncus gravida arcu. 7 | */ 8 | 9 | package com.yollpoll.nmb.di 10 | 11 | import com.yollpoll.framework.net.http.RetrofitFactory 12 | import com.yollpoll.nmb.model.repository.ArticleDetailRepository 13 | import dagger.Module 14 | import dagger.Provides 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.android.components.ServiceComponent 17 | import dagger.hilt.android.components.ViewModelComponent 18 | import dagger.hilt.android.scopes.ViewModelScoped 19 | import dagger.hilt.components.SingletonComponent 20 | import javax.inject.Singleton 21 | 22 | @Module 23 | @InstallIn(SingletonComponent::class)//生命周期 24 | class ArticleDetailViewModelDI { 25 | @Singleton//作用域 26 | @Provides 27 | fun provideRepository(@CommonRetrofitFactory retrofitFactory: RetrofitFactory) = 28 | ArticleDetailRepository(retrofitFactory) 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/CommonDi.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.framework.dispatch.DispatcherManager 4 | import com.yollpoll.framework.net.http.RetrofitFactory 5 | import com.yollpoll.ilog.Ilog 6 | import com.yollpoll.log.ILogImpl 7 | import com.yollpoll.nmb.Iqr 8 | import com.yollpoll.nmb.model.repository.ArticleDetailRepository 9 | import com.yollpoll.nmb.router.DispatchClient 10 | import com.yollpoll.qrlib.QrUtils 11 | import dagger.Binds 12 | import dagger.Module 13 | import dagger.Provides 14 | import dagger.hilt.InstallIn 15 | import dagger.hilt.components.SingletonComponent 16 | import javax.inject.Singleton 17 | 18 | /** 19 | * Created by spq on 2022/1/10 20 | */ 21 | @Module 22 | @InstallIn(SingletonComponent::class) 23 | class DispatchClientDi { 24 | 25 | @Provides 26 | fun getManager(): DispatcherManager { 27 | return DispatchClient.manager 28 | } 29 | } 30 | 31 | @Module 32 | @InstallIn(SingletonComponent::class) 33 | abstract class LogDi { 34 | @Binds 35 | abstract fun bindILog(ilog: ILogImpl): Ilog 36 | } 37 | 38 | @Module 39 | @InstallIn(SingletonComponent::class)//生命周期 40 | abstract class QrDi { 41 | @Binds 42 | abstract fun bindILog(iqr: QrUtils): Iqr 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/DBModuleDI.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.framework.net.http.RetrofitFactory 4 | import com.yollpoll.nmb.db.MainDB 5 | import com.yollpoll.nmb.net.launcherRetrofitFactory 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.scopes.ViewModelScoped 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | class DBModuleDI { 16 | @Singleton 17 | @Provides 18 | fun provideDB(): MainDB { 19 | return MainDB.getInstance() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/DiAnnotation.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | //retrofitFactory 6 | 7 | @Qualifier 8 | @Retention(AnnotationRetention.BINARY) 9 | annotation class CommonRetrofitFactory 10 | 11 | @Qualifier 12 | @Retention(AnnotationRetention.BINARY) 13 | annotation class LauncherRetrofitFactory 14 | 15 | //repository 16 | 17 | @Qualifier 18 | @Retention(AnnotationRetention.BINARY) 19 | annotation class LauncherRepositoryAnnotation 20 | 21 | @Qualifier 22 | @Retention(AnnotationRetention.BINARY) 23 | annotation class HomeRepositoryAnnotation 24 | 25 | @Qualifier 26 | @Retention(AnnotationRetention.BINARY) 27 | annotation class ArticleDetailAnnotation 28 | 29 | @Qualifier 30 | @Retention(AnnotationRetention.BINARY) 31 | annotation class AppWidgetRepository -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/HomeViewModelDI.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.framework.net.http.RetrofitFactory 4 | import com.yollpoll.nmb.model.repository.HomeRepository 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ViewModelComponent 9 | import dagger.hilt.android.scopes.ViewModelScoped 10 | 11 | @InstallIn(ViewModelComponent::class) 12 | @Module 13 | class HomeModel { 14 | @Provides 15 | @ViewModelScoped 16 | fun provideRepository(@CommonRetrofitFactory retrofitFactory: RetrofitFactory): HomeRepository = 17 | HomeRepository(retrofitFactory) 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/LauncherViewModelDI.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.framework.net.http.RetrofitFactory 4 | import com.yollpoll.nmb.model.repository.LauncherRepository 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ViewModelComponent 9 | import dagger.hilt.android.scopes.ViewModelScoped 10 | 11 | @Module 12 | @InstallIn(ViewModelComponent::class) 13 | class LauncherViewModelDI { 14 | @ViewModelScoped 15 | @Provides 16 | fun provideRepository(@LauncherRetrofitFactory retrofitFactory: RetrofitFactory) = 17 | LauncherRepository(retrofitFactory) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/NetModuleDI.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.arch.log.LogUtils 4 | import com.yollpoll.framework.net.http.RetrofitFactory 5 | import com.yollpoll.framework.net.http.RetrofitIntercept 6 | import com.yollpoll.nmb.net.BASE_URL 7 | import com.yollpoll.nmb.net.CoverImgInterceptor 8 | import com.yollpoll.nmb.net.commonRetrofitFactory 9 | import com.yollpoll.nmb.net.launcherRetrofitFactory 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.android.components.ViewModelComponent 14 | import dagger.hilt.android.scopes.ViewModelScoped 15 | import dagger.hilt.components.SingletonComponent 16 | import okhttp3.OkHttpClient 17 | import retrofit2.Retrofit 18 | import javax.inject.Singleton 19 | 20 | @Module 21 | @InstallIn(SingletonComponent::class)//生命周期 22 | class NetModuleDI { 23 | 24 | @Singleton 25 | @Provides 26 | @LauncherRetrofitFactory 27 | fun provideLauncherRetrofitFactory(): RetrofitFactory { 28 | return launcherRetrofitFactory 29 | } 30 | 31 | @Singleton 32 | @Provides 33 | @CommonRetrofitFactory 34 | fun provideCommonRetrofitFactory(): RetrofitFactory { 35 | return commonRetrofitFactory 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/di/RepositoryDI.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.di 2 | 3 | import com.yollpoll.nmb.model.repository.ArticleDetailRepository 4 | import com.yollpoll.nmb.model.repository.HomeRepository 5 | import com.yollpoll.nmb.model.repository.IRepository 6 | import com.yollpoll.nmb.model.repository.LauncherRepository 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.components.ViewModelComponent 11 | 12 | //@Module 13 | //@InstallIn(ViewModelComponent::class) 14 | //abstract class LauncherRepositoryDI { 15 | // @Binds 16 | // @LauncherRepositoryAnnotation 17 | // abstract fun provideRepository(launcherRepository: LauncherRepository): IRepository 18 | // 19 | // @Binds 20 | // @HomeRepositoryAnnotation 21 | // abstract fun provideHomeRepository(launcherRepository: HomeRepository): IRepository 22 | // 23 | // @Binds 24 | // @ArticleDetailAnnotation 25 | // abstract fun provideArticleDetailRepository(repository: ArticleDetailRepository): IRepository 26 | //} -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/Announcement.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | class Announcement(val content: String, val date: Long, val enable: Boolean) { 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/Article.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import androidx.room.* 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | import java.sql.Timestamp 7 | 8 | 9 | //class Article : ArrayList() 10 | typealias Article = ArrayList 11 | 12 | @Entity 13 | @JsonClass(generateAdapter = true) 14 | data class ArticleItem( 15 | var admin: String, 16 | var content: String, 17 | var email: String?, 18 | @ColumnInfo(name = "ext") var ext: String, 19 | @PrimaryKey var id: String, 20 | @ColumnInfo(name = "img") var img: String, 21 | var name: String, 22 | var now: String, 23 | var ReplyCount: String?, 24 | var title: String, 25 | var user_hash: String, 26 | var master: String?,//是否是发帖人 27 | var page: Int = 1, 28 | var sage: Int,//吃我世嘉 29 | var Hide: Int, 30 | var replyTo: String?,//当前回复的串的id 31 | ) { 32 | @Ignore 33 | var Replies: List? = null 34 | 35 | @Ignore 36 | var tagColor: Int? = null//标记 37 | 38 | @Ignore 39 | var index: Int? = null//第x条回复 40 | 41 | 42 | } 43 | 44 | @JsonClass(generateAdapter = true) 45 | data class ImgTuple( 46 | @ColumnInfo(name = "img") val img: String, 47 | @ColumnInfo(name = "ext") val ext: String, 48 | @ColumnInfo(name = "id") val id: String, 49 | ) 50 | 51 | data class PageItem(@ColumnInfo(name = "page") val page: Int) 52 | 53 | @Entity 54 | data class ShieldArticle(@PrimaryKey val articleId: String)//屏蔽列表 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/CookieBean.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.squareup.moshi.Json 6 | import com.squareup.moshi.JsonClass 7 | 8 | @Entity 9 | @JsonClass(generateAdapter = true) 10 | data class CookieBean( 11 | var cookie: String, 12 | @PrimaryKey(autoGenerate = false) var name: String, 13 | var used: Int = 0//0未使用,1使用中 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/DraftBean.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.squareup.moshi.JsonClass 7 | 8 | /** 9 | * Created by spq on 2022/12/6 10 | */ 11 | @JsonClass(generateAdapter = true) 12 | @Entity(tableName = "draft") 13 | data class DraftBean( 14 | var reply: String?, 15 | val fid: String, 16 | @ColumnInfo(name = "f_name") 17 | val fName: String,//板块名称 18 | val mask: String, 19 | val email: String?, 20 | @PrimaryKey(autoGenerate = true) 21 | val id: Int? = 0, 22 | val title: String?, 23 | val content: String, 24 | @ColumnInfo(name = "update_time") val updateTime: Long, 25 | val img: String? 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/Fourm.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.squareup.moshi.JsonClass 6 | 7 | class ForumList : ArrayList() 8 | 9 | 10 | //板块 11 | @JsonClass(generateAdapter = true) 12 | data class Forum( 13 | val forums: List, 14 | val id: String, 15 | val name: String, 16 | val sort: String, 17 | val status: String 18 | ) 19 | 20 | //详细版本 21 | @JsonClass(generateAdapter = true) 22 | @Entity(tableName = "forum") 23 | data class ForumDetail( 24 | var createdAt: String?, 25 | var fgroup: String?, 26 | @PrimaryKey 27 | val id: String, 28 | var interval: String?, 29 | var msg: String, 30 | var name: String, 31 | var showName: String?, 32 | var sort: String?, 33 | var status: String?, 34 | var updateAt: String?, 35 | var show: Int = 1 36 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/HistoryBean.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.squareup.moshi.JsonClass 7 | 8 | /** 9 | * Created by spq on 2022/11/11 10 | */ 11 | @Entity 12 | data class HistoryBean( 13 | val admin: String, 14 | val content: String, 15 | val email: String?, 16 | @PrimaryKey 17 | val id: Int, 18 | val name: String, 19 | var now: String, 20 | val resto: String?, 21 | val sage: String?, 22 | val title: String, 23 | val user_hash: String, 24 | @ColumnInfo(name = "update_time",defaultValue = "'CURRENT_TIMESTAMP'") 25 | var updateTime: Long, 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/MySpeechBean.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | @Entity 9 | data class MySpeechBean( 10 | val admin: Int, 11 | val content: String, 12 | val email: String, 13 | @PrimaryKey 14 | val id: Int, 15 | val name: String, 16 | val now: String, 17 | val resto: Int, 18 | val sage: Int, 19 | val title: String, 20 | val user_hash: String 21 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/bean/SettingBean.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.bean 2 | 3 | import com.yollpoll.skin.SkinTheme 4 | 5 | /** 6 | * Created by spq on 2022/11/21 7 | */ 8 | data class SettingBean( 9 | val uiTheme: SkinTheme, 10 | val darkMod: DarkMod, 11 | val collectionId: String, 12 | val noImage: Boolean, 13 | val noCookie: Boolean, 14 | val thumbBigImg: Boolean, 15 | ) 16 | 17 | enum class DarkMod(name: String) { 18 | DARK("黑暗"), LIGHT("光明"), AUTO("自动") 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/repository/CookieRepository.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.repository 2 | 3 | import com.yollpoll.nmb.db.MainDB 4 | import com.yollpoll.nmb.model.bean.CookieBean 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import javax.inject.Inject 8 | 9 | class CookieRepository @Inject constructor(val db: MainDB) : IRepository { 10 | suspend fun queryCookies(): List = withContext(Dispatchers.IO) { 11 | db.getCookieDao().queryAll() 12 | } 13 | 14 | suspend fun insertCookie(vararg cookie: CookieBean) = withContext(Dispatchers.IO) { 15 | db.getCookieDao().insertAll(cookie.toList()) 16 | } 17 | 18 | suspend fun updateCookie(cookie: CookieBean) = withContext(Dispatchers.IO) { 19 | db.getCookieDao().update(cookie) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/repository/DraftRepository.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.repository 2 | 3 | import com.yollpoll.nmb.db.MainDB 4 | import com.yollpoll.nmb.model.bean.DraftBean 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by spq on 2022/12/6 10 | */ 11 | class DraftRepository @Inject constructor(val db: MainDB) : IRepository { 12 | suspend fun getDrafts(): List { 13 | return db.getDraftDao().query() 14 | } 15 | 16 | fun getDraftFlow(): Flow> { 17 | return db.getDraftDao().queryFlow() 18 | } 19 | 20 | suspend fun delDraft(bean: DraftBean) { 21 | db.getDraftDao().delete(bean) 22 | } 23 | 24 | suspend fun insetDraft(vararg bean: DraftBean) { 25 | db.getDraftDao().insert(*bean) 26 | } 27 | 28 | suspend fun updateDraft(vararg bean: DraftBean) { 29 | db.getDraftDao().update(*bean) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/repository/IRepository.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.repository 2 | 3 | interface IRepository { 4 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/model/repository/LauncherRepository.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.model.repository 2 | 3 | import android.util.Log 4 | import com.yollpoll.framework.net.http.RetrofitFactory 5 | import com.yollpoll.nmb.TAG 6 | import com.yollpoll.nmb.di.LauncherRetrofitFactory 7 | import com.yollpoll.nmb.net.DIRECT_BASE_URL 8 | import com.yollpoll.nmb.net.HttpService 9 | import com.yollpoll.nmb.net.realUrl 10 | import java.lang.Exception 11 | import javax.inject.Inject 12 | 13 | 14 | class LauncherRepository @Inject constructor( 15 | @LauncherRetrofitFactory val retrofitFactory: RetrofitFactory 16 | ) : IRepository { 17 | private val service by lazy { 18 | retrofitFactory.createService(HttpService::class.java) 19 | } 20 | 21 | @Throws(Exception::class) 22 | suspend fun loadRealUrl() { 23 | try { 24 | val url = service.getRealUrl() 25 | realUrl = url[0] 26 | Log.d(TAG, "loadRealUrl: $realUrl") 27 | } catch (e: Exception) { 28 | realUrl = DIRECT_BASE_URL 29 | throw Exception("获取真实url失败:${e.message}") 30 | } 31 | } 32 | 33 | suspend fun getForumList() = service.getForumList() 34 | 35 | //获取封面真实地址 36 | suspend fun refreshCover() = service.refreshCover() 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/net/Cookie.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.net 2 | 3 | import androidx.datastore.preferences.preferencesDataStore 4 | import com.yollpoll.base.logI 5 | import com.yollpoll.framework.extensions.getBean 6 | import com.yollpoll.nmb.App 7 | import com.yollpoll.nmb.model.bean.CookieBean 8 | import kotlinx.coroutines.GlobalScope 9 | import kotlinx.coroutines.async 10 | import kotlinx.coroutines.launch 11 | import kotlinx.coroutines.runBlocking 12 | import okhttp3.Cookie 13 | import okhttp3.CookieJar 14 | import okhttp3.HttpUrl 15 | import java.net.URL 16 | 17 | val cookieStore = hashMapOf>() 18 | 19 | class LocalCookieJar : CookieJar { 20 | override fun saveFromResponse(url: HttpUrl, cookies: MutableList) { 21 | cookieStore[url.host()] = cookies 22 | } 23 | 24 | override fun loadForRequest(url: HttpUrl): MutableList { 25 | val builder = Cookie.Builder() 26 | val list = arrayListOf() 27 | App.INSTANCE.cookie?.run { 28 | "find cookie: ${this.name}".logI() 29 | val cookie = 30 | builder.name("userhash").value(this.cookie).domain(url.host()).build() 31 | list.add(cookie) 32 | } 33 | return list 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/net/CoverImgInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.net 2 | 3 | import android.util.Log 4 | import com.yollpoll.base.logI 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | 8 | private const val TAG = "CoverImgInterceptor" 9 | 10 | class CoverImgInterceptor : Interceptor { 11 | override fun intercept(chain: Interceptor.Chain): Response { 12 | try { 13 | val orgUrl = chain.request().url().toString() 14 | "url:${orgUrl}".logI() 15 | val response = chain.proceed(chain.request()) 16 | val realUrl = response.request().url().toString() 17 | if (orgUrl == COVER) { 18 | Log.d(TAG, "intercept: realUrl$realUrl") 19 | //拦截到封面请求 20 | realCover = realUrl 21 | } 22 | return response 23 | } catch (e: Exception) { 24 | 25 | } 26 | return chain.proceed(chain.request()) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/net/HttpServiceFactory.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.net 2 | 3 | import com.yollpoll.arch.log.LogUtils 4 | import com.yollpoll.framework.extensions.shortToast 5 | import com.yollpoll.framework.net.http.RetrofitFactory 6 | import com.yollpoll.framework.net.http.RetrofitIntercept 7 | import okhttp3.OkHttpClient 8 | import retrofit2.Retrofit 9 | 10 | val launcherRetrofitFactory by lazy { 11 | RetrofitFactory(object : RetrofitIntercept { 12 | override fun baseUrl(): String { 13 | return BASE_URL 14 | } 15 | 16 | override fun okHttpClient(client: OkHttpClient) { 17 | } 18 | 19 | override fun okHttpClientBuilder(builder: OkHttpClient.Builder) { 20 | builder.addInterceptor(CoverImgInterceptor()) 21 | 22 | } 23 | 24 | 25 | override fun retrofit(retrofit: Retrofit) { 26 | } 27 | 28 | override fun retrofitBuilder(builder: Retrofit.Builder) { 29 | // builder.addConverterFactory(MoshiConverterFactory.create()) 30 | } 31 | 32 | }) 33 | } 34 | val commonRetrofitFactory by lazy { 35 | RetrofitFactory(object : RetrofitIntercept { 36 | override fun baseUrl(): String { 37 | return BASE_URL 38 | } 39 | 40 | override fun okHttpClient(client: OkHttpClient) { 41 | } 42 | 43 | override fun okHttpClientBuilder(builder: OkHttpClient.Builder) { 44 | builder.addInterceptor(LoggerInterceptor()) 45 | builder.addInterceptor(CoverImgInterceptor()) 46 | builder.addInterceptor(NMBInterceptor()) 47 | builder.cookieJar(LocalCookieJar()) 48 | 49 | } 50 | 51 | 52 | override fun retrofit(retrofit: Retrofit) { 53 | } 54 | 55 | override fun retrofitBuilder(builder: Retrofit.Builder) { 56 | // builder.addConverterFactory(MoshiConverterFactory.create()) 57 | } 58 | 59 | }) 60 | 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/net/NMBInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.net 2 | 3 | import com.yollpoll.nmb.APP_ID 4 | import com.yollpoll.nmb.USER_AGENT 5 | import okhttp3.Headers 6 | import okhttp3.Interceptor 7 | import okhttp3.Request 8 | import okhttp3.Response 9 | 10 | class NMBInterceptor : Interceptor { 11 | //header 12 | private val headers = hashMapOf( 13 | "User-Agent" to USER_AGENT 14 | ) 15 | private val commonParams = hashMapOf( 16 | "appid" to APP_ID 17 | ) 18 | 19 | override fun intercept(chain: Interceptor.Chain): Response { 20 | val oldRequest = chain.request() 21 | val builder = oldRequest.newBuilder() 22 | injectParams(oldRequest, builder, commonParams) 23 | val newRequest = builder.headers(Headers.of(headers)).build() 24 | return chain.proceed(newRequest) 25 | } 26 | 27 | 28 | //注入参数 29 | private fun injectParams( 30 | request: Request, 31 | builder: Request.Builder, 32 | params: Map 33 | ) { 34 | val httpUrlBuilder = request.url().newBuilder() 35 | params.forEach { 36 | httpUrlBuilder.addQueryParameter(it.key, it.value) 37 | } 38 | builder.url(httpUrlBuilder.build()) 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/net/Url.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.net 2 | 3 | 4 | const val DOMAIN = "google.com" 5 | const val BASE_URL = "https://www.nmbxd1.com/" 6 | const val DIRECT_BASE_URL = "https://adnmb3.com/"//重定向以后的url,应该动态获取 7 | 8 | const val ROOT_URL = "Api/backupUrl"//重定向获取根url 9 | 10 | const val CDN_URL = "Api/getCdnPath"//图片cdn地址 11 | const val COVER = "http://nmb.ovear.info/h.php"//封面地址,会重定向 12 | var realCover = ""//封面图片重定向以后的真实地址 13 | const val ANNOUNCEMENT = "http://nmb.ovear.info/nmb-notice.json"//公告 14 | const val IMG_THUMB_URL = "/thumb/" 15 | const val IMG_URL = "/image/" 16 | 17 | //获取板块列表 18 | const val FORUM_LIST = "Api/getForumList" 19 | 20 | //获取串 21 | const val GET_ARTICLE = "Api/showf/" 22 | const val GET_CHILD_ARTICLE = "Api/thread" 23 | 24 | const val NEW_THREAD: String = "Home/Forum/doPostThread.html" 25 | const val REPLY_THREAD: String = "Home/Forum/doReplyThread.html" 26 | 27 | //查看订阅 28 | const val COLLECTION: String = "/Api/feed" 29 | const val ADD_COLLECTION: String = "/Api/addFeed" 30 | const val DEL_COLLECTION: String = "/Api/delFeed" 31 | 32 | //时间线 33 | // public static final String TIME_LINE = "Api/timeline"; 34 | const val TIME_LINE: String = "Api/timeline/" 35 | const val TIME_LINE_ID = "-1" 36 | 37 | var realUrl: String? = null//重定向以后获取到的根url 38 | 39 | var imagHead = "https://image.nmb.best"//应该动态获取 40 | var imgUrl = imagHead + IMG_URL 41 | var imgThumbUrl = imagHead + IMG_THUMB_URL 42 | 43 | //发言历史 44 | const val SPEAKING_HISTORY = "/Api/getLastPost" -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/net/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.net 2 | 3 | import com.yollpoll.base.logI 4 | import okhttp3.MediaType 5 | import okhttp3.MultipartBody 6 | import okhttp3.RequestBody 7 | import java.io.File 8 | 9 | /** 10 | * 根据文件创建requestbody 11 | * 12 | * @param file 13 | * @return 14 | */ 15 | fun getRequestBody(file: File?): RequestBody? { 16 | if (file == null) { 17 | return null 18 | } 19 | // 创建 RequestBody,用于封装构建RequestBody 20 | return RequestBody.create(MediaType.parse("image/*"), file) 21 | } 22 | 23 | /** 24 | * 根据文件创建requestbody 25 | * 26 | * @param content 27 | * @return 28 | */ 29 | fun getRequestBody(content: String?): RequestBody? { 30 | if (content == null) { 31 | return null 32 | } 33 | "getReqbody ".logI() 34 | // 创建 RequestBody,用于封装构建RequestBody 35 | return RequestBody.create(MediaType.parse("text/plain"), content) 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/router/Route.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.router 2 | 3 | /** 4 | * Created by spq on 2022/6/27 5 | */ 6 | const val ROUTE_LAUNCHER = "native://nmb?module=launcher" 7 | const val ROUTE_HOME = "native://nmb?module=home" 8 | const val ROUTE_THREAD_DETAIL = "native://nmb?module=thread_detail" 9 | const val ROUTE_IMAGE = "native://nmb?module=img" 10 | const val ROUTE_THREAD_IMAGE = "native://nmb?module=thread_img" 11 | const val ROUTE_COOKIE="native://nmb?module=cookie" 12 | //const val ROUTE_QR="native://nmb?module=qrcode" 13 | const val ROUTE_QR="native://qrlib?module=qrcode" 14 | const val ROUTE_NEW_THREAD="native://nmb?module=new_thread" 15 | const val ROUTE_CHOOSE_TAG="native://nmb?module=choose_tag" 16 | const val ROUTE_COLLECTION="native://nmb?module=collection" 17 | const val ROUTE_MY_SPEECH="native://nmb?module=my_speech" 18 | const val ROUTE_DRAW="native://nmb?module=draw" 19 | const val ROUTE_AUTHOR="native://nmb?module=author" 20 | const val ROUTE_SETTING="native://nmb?module=setting" 21 | const val ROUTE_HISTORY="native://nmb?module=history" 22 | const val ROUTE_SHIELD_LIST="native://nmb?module=shield" 23 | const val ROUTE_FORUM_SETTING="native://nmb?module=forum_setting"//板块配置 24 | const val ROUTE_DRAFT="native://nmb?module=draft"//草稿箱 25 | const val ROUTE_PAY="native://nmb?module=pay"//打赏 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/BindAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import com.bumptech.glide.Glide 6 | import com.yollpoll.nmb.net.imgThumbUrl 7 | import com.yollpoll.nmb.net.imgUrl 8 | 9 | class BindAdapter { 10 | companion object { 11 | @BindingAdapter(value = ["thumbUrl"], requireAll = true) 12 | fun thumbUrl(view: ImageView, url: String) { 13 | //图片加载 14 | Glide.with(view.context) 15 | .asBitmap() 16 | .apply(getCommonGlideOptions(view.context)) 17 | .load(imgThumbUrl + url) 18 | .into(view) 19 | } 20 | 21 | @BindingAdapter(value = ["imgUrl"], requireAll = true) 22 | fun realImgUrl(view: ImageView, url: String) { 23 | //图片加载 24 | Glide.with(view.context) 25 | .asBitmap() 26 | .apply(getCommonGlideOptions(view.context)) 27 | .load(imgUrl + url) 28 | .into(view) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/ChangeBurshWidthView.java: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.support.annotation.Nullable; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import com.yollpoll.nmb.R; 11 | 12 | 13 | /** 14 | * Created by 鹏祺 on 2017/6/22. 15 | */ 16 | 17 | public class ChangeBurshWidthView extends View { 18 | private Paint mPaint; 19 | 20 | public ChangeBurshWidthView(Context context) { 21 | super(context); 22 | init(); 23 | } 24 | 25 | public ChangeBurshWidthView(Context context, @Nullable AttributeSet attrs) { 26 | super(context, attrs); 27 | init(); 28 | } 29 | 30 | public ChangeBurshWidthView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | init(); 33 | } 34 | 35 | public void setWidth(int width){ 36 | mPaint.setStrokeWidth(width); 37 | postInvalidate(); 38 | } 39 | private void init() { 40 | mPaint = new Paint(); 41 | mPaint.setColor(getResources().getColor(R.color.black)); 42 | mPaint.setAntiAlias(true); 43 | mPaint.setStrokeWidth(20); 44 | mPaint.setDither(true); 45 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 46 | mPaint.setStrokeCap(Paint.Cap.ROUND); 47 | } 48 | 49 | @Override 50 | protected void onDraw(Canvas canvas) { 51 | super.onDraw(canvas); 52 | canvas.drawLine(0 + getPaddingLeft(), (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / 2, getMeasuredWidth() - getPaddingRight(), 53 | (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / 2, mPaint); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/ImportCollectionDialog.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.view.WindowManager 6 | import com.yollpoll.base.NMBDialog 7 | import com.yollpoll.framework.extensions.dp2px 8 | import com.yollpoll.nmb.R 9 | import com.yollpoll.nmb.databinding.DialogImportCollectionBinding 10 | 11 | /** 12 | * Created by spq on 2022/11/8 13 | */ 14 | class ImportCollectionDialog( 15 | private val mContext: Context?, 16 | private val onOk: ((String?) -> Unit)? = null 17 | ) : 18 | NMBDialog(mContext) { 19 | public var no: String? = null 20 | 21 | override fun getLayoutId() = R.layout.dialog_import_collection 22 | 23 | override fun createDialog(context: Context?) = Dialog(mContext) 24 | 25 | override fun onInit(dialog: Dialog?, binding: DialogImportCollectionBinding?) { 26 | super.onInit(dialog, binding) 27 | binding?.dialog = this 28 | } 29 | 30 | override fun onDialogShow(dialog: Dialog) { 31 | super.onDialogShow(dialog) 32 | val lp: WindowManager.LayoutParams = WindowManager.LayoutParams() 33 | 34 | lp.copyFrom(dialog.window?.attributes) 35 | 36 | lp.width = mContext.dp2px(320F).toInt() 37 | 38 | lp.height = mContext.dp2px(200F).toInt() 39 | 40 | lp.horizontalMargin 41 | 42 | dialog.window?.attributes = lp 43 | } 44 | 45 | fun onOkClick() { 46 | onOk?.invoke(no) 47 | this.dismiss() 48 | } 49 | 50 | fun onCancelClick() { 51 | this.dismiss() 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/InputDialog.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.view.WindowManager 6 | import com.yollpoll.base.NMBDialog 7 | import com.yollpoll.framework.extensions.dp2px 8 | import com.yollpoll.nmb.R 9 | import com.yollpoll.nmb.databinding.DialogImportCollectionBinding 10 | import com.yollpoll.nmb.databinding.DialogInputBinding 11 | 12 | /** 13 | * Created by spq on 2022/11/22 14 | */ 15 | class InputDialog( 16 | private val mContext: Context, 17 | val title: String, 18 | val hint: String, 19 | var content: String? = null, 20 | private val onOk: ((String?) -> Unit)? = null 21 | 22 | ) : NMBDialog(mContext) { 23 | 24 | override fun getLayoutId() = R.layout.dialog_input 25 | 26 | override fun createDialog(context: Context?) = Dialog(mContext) 27 | 28 | override fun onInit(dialog: Dialog?, binding: DialogInputBinding?) { 29 | super.onInit(dialog, binding) 30 | binding?.dialog = this 31 | } 32 | 33 | override fun onDialogShow(dialog: Dialog) { 34 | super.onDialogShow(dialog) 35 | val lp: WindowManager.LayoutParams = WindowManager.LayoutParams() 36 | 37 | lp.copyFrom(dialog.window?.attributes) 38 | 39 | lp.width = mContext.dp2px(320F).toInt() 40 | 41 | lp.height = mContext.dp2px(200F).toInt() 42 | 43 | lp.horizontalMargin 44 | 45 | dialog.window?.attributes = lp 46 | } 47 | 48 | fun onOkClick() { 49 | onOk?.invoke(content) 50 | this.dismiss() 51 | } 52 | 53 | fun onCancelClick() { 54 | this.dismiss() 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/MyFloatingMenu.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets 2 | 3 | class MyFloatingMenu { 4 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/SelectPageDialog.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.widget.SeekBar 6 | import androidx.appcompat.app.AlertDialog 7 | import androidx.core.app.DialogCompat 8 | import com.yollpoll.base.NMBDialog 9 | import com.yollpoll.framework.widgets.BaseDialog 10 | import com.yollpoll.nmb.R 11 | import com.yollpoll.nmb.databinding.DialogSelectPageBinding 12 | 13 | class SelectPageDialog( 14 | private val cur: Int, 15 | private val max: Int, 16 | private val context: Context, 17 | private val onSelected: ((Int) -> Unit)? = null 18 | ) : 19 | NMBDialog(context) { 20 | var selectPage: Int = cur 21 | override fun getLayoutId() = R.layout.dialog_select_page 22 | 23 | override fun createDialog(context: Context) = Dialog(context) 24 | 25 | override fun onInit(dialog: Dialog?, binding: DialogSelectPageBinding) { 26 | binding.max = max 27 | binding.cur = cur 28 | binding.selected = selectPage 29 | binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { 30 | override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { 31 | selectPage = progress + 1 32 | binding.selected = selectPage 33 | binding.executePendingBindings() 34 | } 35 | 36 | override fun onStartTrackingTouch(seekBar: SeekBar?) { 37 | } 38 | 39 | override fun onStopTrackingTouch(seekBar: SeekBar?) { 40 | } 41 | }) 42 | binding.executePendingBindings() 43 | binding.btnOk.setOnClickListener { 44 | onSelected?.invoke(selectPage) 45 | this.dismiss() 46 | } 47 | binding.btnCancel.setOnClickListener { 48 | this.dismiss() 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/emoji/PicEmojiAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets.emoji 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.yollpoll.nmb.R 9 | 10 | /** 11 | * Created by 鹏祺 on 2017/6/15. 12 | */ 13 | class PicEmojiAdapter( 14 | private val list: List, 15 | private val onItemClick: ((View, Int) -> Unit)? 16 | ) : 17 | RecyclerView.Adapter() { 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 19 | val view = 20 | LayoutInflater.from(parent.context).inflate(R.layout.item_pic_emoji, parent, false) 21 | return ViewHolder(view) 22 | } 23 | 24 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 25 | val item = list[position] 26 | holder.imgPicEmoji.setImageResource(item) 27 | holder.imgPicEmoji.setOnClickListener { v -> 28 | onItemClick?.invoke( 29 | v, 30 | position 31 | ) 32 | } 33 | } 34 | 35 | override fun getItemCount(): Int { 36 | return list.size 37 | } 38 | 39 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 40 | var imgPicEmoji: ImageView 41 | 42 | init { 43 | imgPicEmoji = itemView.findViewById(R.id.img_pic_emoji) as ImageView 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/yollpoll/nmb/view/widgets/emoji/WordEmojiAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.yollpoll.nmb.view.widgets.emoji 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.yollpoll.nmb.R 9 | 10 | /** 11 | * Created by 鹏祺 on 2017/6/15. 12 | */ 13 | class WordEmojiAdapter( 14 | private val list: List, 15 | private val onItemClick: ((View, Int) -> Unit)? 16 | ) : 17 | RecyclerView.Adapter() { 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 19 | val view = 20 | LayoutInflater.from(parent.context).inflate(R.layout.item_word_emoji, parent, false) 21 | return ViewHolder(view) 22 | } 23 | 24 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 25 | val item = list[position] 26 | holder.tvWordEomji.text = item 27 | holder.tvWordEomji.setOnClickListener { v -> 28 | onItemClick?.invoke( 29 | v, 30 | holder.adapterPosition 31 | ) 32 | } 33 | } 34 | 35 | override fun getItemCount(): Int { 36 | return list.size 37 | } 38 | 39 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 40 | var tvWordEomji: TextView 41 | 42 | init { 43 | tvWordEomji = itemView.findViewById(R.id.tv_word_emoji) as TextView 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/new_thread_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/new_thread_anim_close.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/new_thread_layout_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_checked.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yollpoll/nmb/c42816ca1cfbfcae7a825f409af76ca3f8eb558c/app/src/main/res/drawable-v24/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/shape_new_thread_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_cookie.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_checked.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_forum.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_forum_hide.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_cookie_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_right_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_collection.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 21 | 22 | 26 | 27 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_draft.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 22 | 23 | 28 | 29 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_forum_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 20 | 21 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 22 | 23 | 27 | 28 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 20 | 21 | 26 | 27 | 33 | 34 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 20 | 21 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_my_speech.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 20 | 21 | 25 | 26 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_qr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_shield.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 21 | 22 | 26 | 27 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_thread_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 20 | 21 | 26 | 27 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_web.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 12 | 13 | 14 | 18 | 19 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/alert_choose_photo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | 23 | 24 | 28 | 29 | 34 | 35 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_brush_width.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_choose_emoji.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 20 | 21 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 24 | 25 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/include_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_cookie.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 25 | 26 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_for_custom_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_forum.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 17 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_pic_emoji.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_setting_forum.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 19 |