├── .circleci └── config.yml ├── .gitignore ├── .idea ├── .gitignore ├── assetWizardSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── dictionaries │ └── fdx.xml ├── jarRepositories.xml ├── kotlinc.xml └── vcs.xml ├── .travis.yml ├── License.txt ├── README-zh.md ├── README.md ├── app ├── build.gradle.kts ├── google-services.json ├── proguard-rules.pro └── src │ ├── debug │ └── res │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── ic_launcher-web.png │ ├── ic_notification_icon-web.png │ ├── ic_settings-web.png │ ├── java │ │ └── im │ │ │ └── fdx │ │ │ └── v2ex │ │ │ ├── MyApp.kt │ │ │ ├── MyGlideModule.kt │ │ │ ├── database │ │ │ ├── AppDatabase.kt │ │ │ └── DbHelper.kt │ │ │ ├── model │ │ │ ├── Data.kt │ │ │ ├── NotificationModel.kt │ │ │ ├── Res.kt │ │ │ └── VModel.kt │ │ │ ├── network │ │ │ ├── Api.kt │ │ │ ├── GetMoreRepliesWorker.kt │ │ │ ├── GetMsgWorker.kt │ │ │ ├── HttpHelper.kt │ │ │ ├── NetManager.kt │ │ │ ├── Parser.kt │ │ │ └── cookie │ │ │ │ ├── CookiePersistor.kt │ │ │ │ ├── MyCookieJar.kt │ │ │ │ └── SharedPrefsPersistor.kt │ │ │ ├── ui │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── MyCallback.kt │ │ │ ├── NotificationActivity.kt │ │ │ ├── NotificationAdapter.kt │ │ │ ├── PhotoActivity.kt │ │ │ ├── SettingsActivity.kt │ │ │ ├── TabSettingActivity.kt │ │ │ ├── TestActivity.kt │ │ │ ├── WebViewActivity.kt │ │ │ ├── favor │ │ │ │ ├── FavorActivity.kt │ │ │ │ ├── FavorViewPagerAdapter.kt │ │ │ │ └── NodeFavorFragment.kt │ │ │ ├── main │ │ │ │ ├── Comment.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MyDiffCallback.kt │ │ │ │ ├── MyViewPagerAdapter.kt │ │ │ │ ├── NewTopicActivity.kt │ │ │ │ ├── SearchActivity.kt │ │ │ │ ├── Topic.kt │ │ │ │ ├── TopicDao.kt │ │ │ │ ├── TopicsFragment.kt │ │ │ │ ├── TopicsRVAdapter.kt │ │ │ │ └── model │ │ │ │ │ └── SearchResult.kt │ │ │ ├── member │ │ │ │ ├── DiffReply.kt │ │ │ │ ├── Member.kt │ │ │ │ ├── MemberActivity.kt │ │ │ │ ├── MemberReplyModel.kt │ │ │ │ ├── ReplyAdapter.kt │ │ │ │ └── UserReplyFragment.kt │ │ │ ├── node │ │ │ │ ├── AllNodesActivity.kt │ │ │ │ ├── AllNodesAdapter.kt │ │ │ │ ├── AllNodesAdapterNew.kt │ │ │ │ ├── Node.kt │ │ │ │ ├── NodeActivity.kt │ │ │ │ ├── NodeDao.kt │ │ │ │ └── SimpleNodesTextAdapter.kt │ │ │ └── topic │ │ │ │ ├── BottomReplyList.kt │ │ │ │ ├── MyReply.kt │ │ │ │ ├── MyReplyDao.kt │ │ │ │ ├── Reply.kt │ │ │ │ ├── TopicActivity.kt │ │ │ │ ├── TopicAdapter.kt │ │ │ │ └── TopicFragment.kt │ │ │ ├── utils │ │ │ ├── EndlessOnScrollListener.kt │ │ │ ├── ImageUtil.kt │ │ │ ├── Keys.kt │ │ │ ├── SecureUtils.java │ │ │ ├── TimeUtil.kt │ │ │ ├── ViewUtil.kt │ │ │ └── extensions │ │ │ │ ├── DpExt.kt │ │ │ │ ├── Hint.kt │ │ │ │ ├── StringExt.kt │ │ │ │ └── ViewExt.kt │ │ │ └── view │ │ │ ├── BitmapHolder.kt │ │ │ ├── BottomSheetMenu.kt │ │ │ ├── CustomChrome.kt │ │ │ ├── GoodTextView.kt │ │ │ ├── NaviItem.kt │ │ │ ├── PageNumberView.kt │ │ │ ├── Popup.kt │ │ │ ├── ViewPagerHelper.kt │ │ │ └── ZoomOutPageTransform.kt │ ├── logo-alpha.png │ ├── logo.png │ └── res │ │ ├── anim │ │ ├── hide_toolbar.xml │ │ ├── node_title_ctl.xml │ │ ├── rotate_refresh.xml │ │ └── show_toolbar.xml │ │ ├── drawable-night │ │ └── ic_night_mode.xml │ │ ├── drawable-xxhdpi │ │ ├── bg_nav_header.png │ │ ├── ic_shortcut_create.png │ │ ├── loading_image.png │ │ └── loading_image_4_3.png │ │ ├── drawable │ │ ├── baseline_arrow_drop_down_24.xml │ │ ├── baseline_close_24.xml │ │ ├── baseline_manage_search_24.xml │ │ ├── border.xml │ │ ├── border_page_num.xml │ │ ├── divider.xml │ │ ├── edit_selector.xml │ │ ├── ic_add_white_24dp.xml │ │ ├── ic_all_node.xml │ │ ├── ic_announcement_hint_24dp.xml │ │ ├── ic_arrow_back_primary_24dp.xml │ │ ├── ic_baseline_account_circle_24.xml │ │ ├── ic_baseline_add_circle_outline_24.xml │ │ ├── ic_baseline_arrow_downward_24.xml │ │ ├── ic_baseline_remove_circle_outline_24.xml │ │ ├── ic_baseline_report_24.xml │ │ ├── ic_bitcoin.xml │ │ ├── ic_block_primary_24dp.xml │ │ ├── ic_block_white_24dp.xml │ │ ├── ic_check_black_24dp.xml │ │ ├── ic_circle.xml │ │ ├── ic_comment.xml │ │ ├── ic_daily_check.xml │ │ ├── ic_dehaze_black_24dp.xml │ │ ├── ic_delete.xml │ │ ├── ic_edit_white_24dp.xml │ │ ├── ic_favorite_black_24dp.xml │ │ ├── ic_favorite_blue_24dp.xml │ │ ├── ic_favorite_border_black_24dp.xml │ │ ├── ic_favorite_border_white_24dp.xml │ │ ├── ic_favorite_white_24dp.xml │ │ ├── ic_github.xml │ │ ├── ic_image.xml │ │ ├── ic_location.xml │ │ ├── ic_message_black_24dp.xml │ │ ├── ic_night_mode.xml │ │ ├── ic_notification_with_red_point.xml │ │ ├── ic_notifications_none_black_24dp.xml │ │ ├── ic_notifications_primary_24dp.xml │ │ ├── ic_notifications_white_24dp.xml │ │ ├── ic_person_outline_black_24dp.xml │ │ ├── ic_profile.xml │ │ ├── ic_refresh_white_24dp.xml │ │ ├── ic_search_primary_24dp.xml │ │ ├── ic_search_white_48px.xml │ │ ├── ic_send_black_24dp.xml │ │ ├── ic_send_hint_24dp.xml │ │ ├── ic_send_primary_24dp.xml │ │ ├── ic_send_white_24dp.xml │ │ ├── ic_settings_24dp.xml │ │ ├── ic_share_24dp.xml │ │ ├── ic_share_white_24dp.xml │ │ ├── ic_thank.xml │ │ ├── ic_thank_new.xml │ │ ├── ic_thumbs_up.xml │ │ ├── ic_twitter.xml │ │ ├── ic_website.xml │ │ ├── ic_zoom_out_map_black_24dp.xml │ │ ├── loading.xml │ │ ├── logo2x.png │ │ ├── radius_2dp.xml │ │ ├── radius_4dp.xml │ │ ├── radius_8dp.xml │ │ ├── radius_outline_4dp.xml │ │ ├── radius_outline_4dp_primary.xml │ │ ├── rounded_close_24.xml │ │ ├── spinner.xml │ │ └── themed_icon.xml │ │ ├── layout │ │ ├── activity_all_nodes.xml │ │ ├── activity_base.xml │ │ ├── activity_create_topic.xml │ │ ├── activity_details.xml │ │ ├── activity_details_content.xml │ │ ├── activity_follow_activity.xml │ │ ├── activity_login.xml │ │ ├── activity_main_content.xml │ │ ├── activity_main_nav_drawer.xml │ │ ├── activity_member.xml │ │ ├── activity_node.xml │ │ ├── activity_notification.xml │ │ ├── activity_photo.xml │ │ ├── activity_search_result.xml │ │ ├── activity_settings.xml │ │ ├── activity_tab_setting.xml │ │ ├── activity_test.xml │ │ ├── activity_web_view.xml │ │ ├── app_toolbar.xml │ │ ├── appbar.xml │ │ ├── bottom_list_sheet.xml │ │ ├── dialog_et.xml │ │ ├── divider.xml │ │ ├── fragment_tab_article.xml │ │ ├── item_all_nodes.xml │ │ ├── item_comments.xml │ │ ├── item_load_more.xml │ │ ├── item_node.xml │ │ ├── item_node_simple.xml │ │ ├── item_node_with_category.xml │ │ ├── item_notification.xml │ │ ├── item_reply_member.xml │ │ ├── item_reply_view.xml │ │ ├── item_text_switch.xml │ │ ├── item_topic_view.xml │ │ ├── item_topic_with_comments.xml │ │ ├── iv_refresh.xml │ │ ├── navi_item.xml │ │ ├── page_item.xml │ │ ├── pager_item.xml │ │ ├── popwindow_bottom.xml │ │ ├── simple_list_item.xml │ │ ├── simple_list_item_center.xml │ │ ├── text_view_spinner.xml │ │ └── view_page_number.xml │ │ ├── menu │ │ ├── menu_activity_nav_drawer.xml │ │ ├── menu_all_node.xml │ │ ├── menu_details.xml │ │ ├── menu_main.xml │ │ ├── menu_member.xml │ │ ├── menu_node.xml │ │ ├── menu_reply.xml │ │ ├── menu_search.xml │ │ ├── menu_tab_setting.xml │ │ └── menu_topic_fragement.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── ic_notification_icon.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── ic_notification_icon.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── ic_notification_icon.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── ic_notification_icon.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── ic_notification_icon.png │ │ ├── transition │ │ ├── change_image_transform.xml │ │ └── explode.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── attrs_navi_item.xml │ │ ├── attrs_page_number_view.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ ├── xml-v25 │ │ └── shortcuts.xml │ │ └── xml │ │ ├── backup_descriptor.xml │ │ ├── preference.xml │ │ ├── preference_login.xml │ │ ├── provider_paths.xml │ │ ├── remote_config_defaults.xml │ │ └── searchable.xml │ └── testDebug │ └── java │ └── im │ └── fdx │ └── v2ex │ ├── network │ ├── HttpHelperTest.kt │ └── JsonManagerTest.kt │ └── utils │ ├── ContentUtilsTest.kt │ ├── SecureUtilsTest.kt │ ├── StringTest.kt │ ├── TimeUtilTest.kt │ └── extensions │ └── StringExtKtTest.kt ├── build.gradle.kts ├── fastlane ├── Appfile ├── Fastfile ├── README.md └── metadata │ └── android │ └── zh-CN │ ├── changelogs │ ├── 61.txt │ ├── 62.txt │ ├── 66.txt │ ├── 69.txt │ └── default.txt │ ├── full_description.txt │ ├── images │ ├── featureGraphic.png │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1_zh-CN.jpeg │ │ ├── 2_zh-CN.jpeg │ │ ├── 3_zh-CN.jpeg │ │ ├── 4_zh-CN.jpeg │ │ └── 5_zh-CN.jpeg │ ├── short_description.txt │ ├── title.txt │ └── video.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── privacy.md ├── searchablespinnerlibrary ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── toptoche │ │ └── searchablespinnerlibrary │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── toptoche │ │ └── searchablespinnerlibrary │ │ ├── SearchableListDialog.java │ │ └── SearchableSpinner.java │ └── res │ ├── layout │ └── searchable_list_dialog.xml │ ├── nobleltevzwLMY47XMeditab02192016201518.gif │ └── values │ ├── attrs.xml │ └── strings.xml └── settings.gradle.kts /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/code 5 | docker: 6 | - image: circleci/android:api-30-alpha 7 | # auth: 8 | # username: mydockerhub-user 9 | # password: $DOCKERHUB_PASSWORD # context / project UI env-var reference 10 | environment: 11 | JVM_OPTS: -Xmx3200m 12 | steps: 13 | - checkout 14 | - restore_cache: 15 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 16 | # - run: 17 | # name: Chmod permissions #if permission for Gradlew Dependencies fail, use this. 18 | # command: sudo chmod +x ./gradlew 19 | - run: 20 | name: Download Dependencies 21 | command: ./gradlew androidDependencies 22 | - save_cache: 23 | paths: 24 | - ~/.gradle 25 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 26 | - run: 27 | name: Run Tests 28 | command: ./gradlew lint test 29 | - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ 30 | path: app/build/reports 31 | destination: reports 32 | - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ 33 | path: app/build/test-results 34 | # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | /.idea/caches 6 | /app/build 7 | /.idea/misc.xml 8 | /.idea/gradle.xml 9 | /.idea/profile_settings.xml 10 | *.iml 11 | /build 12 | .DS_Store 13 | /captures 14 | /app/app-release.apk 15 | keystore.properties 16 | *.apk 17 | *.aab 18 | output.json 19 | /app/release/output-metadata.json 20 | /fastlane/report.xml 21 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | shelf/ -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/dictionaries/fdx.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | crashlytic 5 | horz 6 | louzu 7 | objs 8 | persistor 9 | signin 10 | signup 11 | unfavor 12 | unfavorite 13 | vert 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | android: 3 | components: 4 | - tools 5 | - build-tools-27.0.3 6 | - platform-tools 7 | - android-27 8 | - extra-android-m2repository 9 | - extra-android-support 10 | - extra-google-m2repository 11 | licenses: 12 | - android-sdk-preview-license-52d11cd2 13 | - android-sdk-license-.+ 14 | - google-gdk-license-.+ 15 | - ".+" 16 | jdk: 17 | - oraclejdk8 18 | before_install: 19 | - mkdir "$ANDROID_HOME/licenses" || true 20 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55\nd56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" 21 | - echo -e "\n7c928e048b455a44b323aba54342415d0429c542\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" 22 | before_cache: 23 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 24 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 25 | cache: 26 | directories: 27 | - "$HOME/.gradle/caches/" 28 | - "$HOME/.gradle/wrapper/" 29 | script: "./gradlew assembleDebug" 30 | deploy: 31 | provider: releases 32 | api_key: 33 | secure: xUR2U9bmUTzNmlQoUmiPhP++vsO25W4xBTs6aXMZ2/D+6hkCbh47SKgYuSPV/Fh2HMBqz+dxqe8xk9tt+MGKY3WwjbVbWE8f2rsFNdH4i6XPJiwROG5M+mz/jXAP63R65Uq2lCuUHTUxT6lcEj7jfyQqlyY51e5lg1zepywSUslhE5Q3Ji9fCDY4cMKhO5y16tTMDnlN/W5UA1nKCMH5JCuKYgA9IKwWzx7Wm9WqoN+4P8/4BZiZYkvG7nN1utNzi3feEX4A2MEj7Vs8mcfCpa5QOEQi2d6+abqk7yCZYB5nMn+M2glTeFw305i8OzUdsNhYHdSFZpqos8kH3iSxmg6xWE30mW53kkNSYVW4sqw12xeNgxKaK9ByDUxdqCVdWopOB+wDfkdPyhQ3yI/dtyLNRz9qbKJuHFyN7NDnhpdMs0nqyGgKSIDWf3hZBpp4rCZ66fAYeUGr1Zv++9z8Tje/xgZM3XThtBZyC5ojP14n0SXql/fBv8P58Hq0FCAldxkhshK444jvsMvEmgpa98kalOG9hT862rTsPK73F/hx+CclgI6usk30PagSNIjLlqyNy5Bz9dzvsfczqmiFscp+xxaCHuV8j/ipomvkamhUnNB2snmQ+oqtRUdV3XDRnaYwsyc3IdHwQqHSds55ydxJ82fPB07g41NXYBIeNUQ= 34 | file: app/build/outputs/apk/debug/app-debug.apk 35 | skip_cleanup: true 36 | on: 37 | tags: true 38 | 39 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # v2ex-simple 2 | 3 | 顾名思义,这是一款简单的v2ex第三方Android客户端。 4 | 5 | ## 现状 6 | 7 | 该项目应该不会有大的功能更新了,仅修复bug。如有问题,欢迎提出。 8 | 9 | 10 | [![Build Status](https://travis-ci.org/fan123199/v2ex-simple.svg?branch=master)](https://travis-ci.org/fan123199/v2ex-simple) 11 | 12 | ## Google Play 13 | 14 | [![Download on Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=im.fdx.v2ex) 15 | 16 | 17 | 18 | ## 主要功能 19 | 20 | + 浏览V2EX的内容 21 | + 发表文章和回复 22 | + 从web打开应用 23 | + 后台获取更新 24 | + 夜间模式 25 | + 文字大小配置 26 | + 搜索文章 27 | 28 | ## 用到的第三方库 29 | 30 | + Firebase 31 | + Glide4 32 | + OkHttp3 33 | + Firebase 34 | + Flexbox 35 | + Gson 36 | + Anko 37 | + CircleImageView 38 | + Jsoup 39 | + ... 40 | 41 | ## 鸣谢 42 | 43 | + [greatyao/v2ex-android](https://github.com/greatyao/v2ex-android/tree/master) 44 | 这是我的入门指导,当时还什么都不会。 45 | + 部分UI 由 Evermoon 设计完成 [weibo_link](http://weibo.com/evermoon30964) 46 | 47 | ## 开源许可证 48 | 49 | Copyright 2017 fan123199 50 | 51 | Licensed under the Apache License, Version 2.0 (the "License"); 52 | you may not use this file except in compliance with the License. 53 | You may obtain a copy of the License at 54 | 55 | http://www.apache.org/licenses/LICENSE-2.0 56 | 57 | Unless required by applicable law or agreed to in writing, software 58 | distributed under the License is distributed on an "AS IS" BASIS, 59 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 60 | See the License for the specific language governing permissions and 61 | limitations under the License. 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v2ex-simple 2 | A simple android client for exploring V2EX. Excellent user experience. 3 | Project is first written by Java and now kotlin. 4 | 5 | [中文Readme](./README-zh.md) 6 | 7 | ## status 8 | 9 | The project would will be less feature update, only bug fixed. But you have any issue, I'd like to help you. 10 | 11 | [![Build Status](https://travis-ci.org/fan123199/v2ex-simple.svg?branch=master)](https://travis-ci.org/fan123199/v2ex-simple) 12 | 13 | ## Download 14 | 15 | [Get it on F-Droid](https://f-droid.org/packages/im.fdx.v2ex/) 18 | [Get it on Google Play](https://play.google.com/store/apps/details?id=im.fdx.v2ex) 21 | 22 | ## Main function 23 | 24 | + show v2ex topic content 25 | + create topic and make comment 26 | + open from web url 27 | + unread message through background 28 | + night mode 29 | + font size customized 30 | + search topic 31 | 32 | ## 3rd libraries 33 | 34 | + Firebase 35 | + Glide4 36 | + OkHttp3 37 | + Firebase 38 | + Flexbox 39 | + Gson 40 | + Anko 41 | + CircleImageView 42 | + Jsoup 43 | + ... 44 | 45 | ## Thanks 46 | 47 | + [greatyao/v2ex-android](https://github.com/greatyao/v2ex-android/tree/master) 48 | The project guide me to start this project from zero. 49 | + *twitter/v2ex+/weico/...* is learned for excellent user experience 50 | + UI mostly designed by Evermoon [weibo_link](http://weibo.com/evermoon30964) 51 | 52 | ## License 53 | 54 | Copyright 2017 fan123199 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. 67 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "517796818811", 4 | "firebase_url": "https://v2ex-simple.firebaseio.com", 5 | "project_id": "v2ex-simple", 6 | "storage_bucket": "v2ex-simple.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:517796818811:android:6384266b081c14be", 12 | "android_client_info": { 13 | "package_name": "im.fdx.v2ex" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "517796818811-be2ttlkv98gc3vats7ieht83qgj05t86.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "im.fdx.v2ex", 22 | "certificate_hash": "8197be9af81d80ebd15ef8f411c322503bbee853" 23 | } 24 | }, 25 | { 26 | "client_id": "517796818811-ff8hd6s820c6gugrq9tpeicrd071v8f5.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyBs6Rm6Fa7GTg2vRXSIwYo5fb4PkPY2HGQ" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "517796818811-ff8hd6s820c6gugrq9tpeicrd071v8f5.apps.googleusercontent.com", 40 | "client_type": 3 41 | } 42 | ] 43 | } 44 | } 45 | }, 46 | { 47 | "client_info": { 48 | "mobilesdk_app_id": "1:517796818811:android:77a9c1e989fda93d2457de", 49 | "android_client_info": { 50 | "package_name": "im.fdx.v2ex.debug" 51 | } 52 | }, 53 | "oauth_client": [ 54 | { 55 | "client_id": "517796818811-ff8hd6s820c6gugrq9tpeicrd071v8f5.apps.googleusercontent.com", 56 | "client_type": 3 57 | } 58 | ], 59 | "api_key": [ 60 | { 61 | "current_key": "AIzaSyBs6Rm6Fa7GTg2vRXSIwYo5fb4PkPY2HGQ" 62 | } 63 | ], 64 | "services": { 65 | "appinvite_service": { 66 | "other_platform_oauth_client": [ 67 | { 68 | "client_id": "517796818811-ff8hd6s820c6gugrq9tpeicrd071v8f5.apps.googleusercontent.com", 69 | "client_type": 3 70 | } 71 | ] 72 | } 73 | } 74 | } 75 | ], 76 | "configuration_version": "1" 77 | } -------------------------------------------------------------------------------- /app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | v2ex-beta 4 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/ic_notification_icon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/ic_notification_icon-web.png -------------------------------------------------------------------------------- /app/src/main/ic_settings-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/ic_settings-web.png -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/MyApp.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.SharedPreferences 7 | import androidx.appcompat.app.AppCompatDelegate.* 8 | import androidx.core.content.edit 9 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 10 | import com.elvishew.xlog.LogLevel 11 | import com.elvishew.xlog.XLog 12 | import im.fdx.v2ex.network.HttpHelper 13 | import im.fdx.v2ex.network.cookie.SharedPrefsPersistor 14 | import im.fdx.v2ex.utils.Keys 15 | import im.fdx.v2ex.utils.extensions.logd 16 | import java.net.HttpCookie 17 | 18 | val pref: SharedPreferences by lazy { 19 | androidx.preference.PreferenceManager.getDefaultSharedPreferences(myApp) 20 | } 21 | 22 | @Deprecated("技术困难,下一期实现") 23 | val userPref: SharedPreferences by lazy { 24 | val fileName = pref.getString(Keys.KEY_USERNAME, "user") 25 | myApp.getSharedPreferences(fileName, Context.MODE_PRIVATE) 26 | } 27 | 28 | val myApp: MyApp by lazy { 29 | MyApp.get() 30 | } 31 | 32 | /** 33 | * Created by fdx on 2015/8/16. 34 | * 用于启动时获取app状态 35 | */ 36 | class MyApp : Application() { 37 | 38 | companion object { 39 | private lateinit var INSTANCE: MyApp 40 | internal fun get(): MyApp { 41 | return INSTANCE 42 | } 43 | } 44 | internal var isLogin = false 45 | 46 | override fun onCreate() { 47 | super.onCreate() 48 | INSTANCE = this 49 | XLog.init(when { 50 | BuildConfig.DEBUG -> LogLevel.ALL 51 | else -> LogLevel.NONE 52 | }) 53 | isLogin = pref.getBoolean(Keys.PREF_KEY_IS_LOGIN, false) 54 | 55 | 56 | pref.edit { 57 | putInt(Keys.PREF_APP_PREF_VERSION, BuildConfig.VERSION_CODE) 58 | } 59 | //后续做sp的版本判断在这里 60 | logd("onCreate\nisLogin:$isLogin") 61 | 62 | setDefaultNightMode(pref.getString(Keys.PREF_NIGHT_MODE, MODE_NIGHT_NO.toString())!!.toInt()) 63 | } 64 | } 65 | 66 | fun setLogin(login: Boolean) { 67 | myApp.isLogin = login 68 | pref.edit { 69 | putBoolean(Keys.PREF_KEY_IS_LOGIN, login) 70 | } 71 | if(!login){ 72 | HttpHelper.myCookieJar.clear() 73 | } 74 | LocalBroadcastManager.getInstance(myApp).sendBroadcast( 75 | if (login){ 76 | Intent(Keys.ACTION_LOGIN) 77 | } else { 78 | Intent(Keys.ACTION_LOGOUT) 79 | }) 80 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/MyGlideModule.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | /** 7 | * Created by fdx on 2017/8/29. 8 | * 9 | */ 10 | @GlideModule 11 | class MyGlideModule : AppGlideModule() -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import im.fdx.v2ex.ui.topic.MyReply 6 | import im.fdx.v2ex.ui.topic.MyReplyDao 7 | import im.fdx.v2ex.ui.main.Topic 8 | import im.fdx.v2ex.ui.main.TopicDao 9 | import im.fdx.v2ex.ui.node.Node 10 | import im.fdx.v2ex.ui.node.NodeDao 11 | 12 | 13 | @Database(entities = [Topic::class, Node::class, MyReply::class], version = 5, exportSchema = false) 14 | abstract class AppDatabase : RoomDatabase() { 15 | abstract fun topicDao(): TopicDao 16 | abstract fun nodeDao(): NodeDao 17 | abstract fun myReplyDao(): MyReplyDao 18 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/database/DbHelper.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.database 2 | 3 | import androidx.room.Room 4 | import im.fdx.v2ex.MyApp 5 | 6 | 7 | object DbHelper { 8 | 9 | var db = Room.databaseBuilder(MyApp.get(), AppDatabase::class.java, "v2ex.db") 10 | .fallbackToDestructiveMigration() 11 | .build() 12 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/model/Data.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Data( 6 | 7 | @field:SerializedName("path") 8 | val path: String? = null, 9 | 10 | @SerializedName("filename") 11 | val filename: String? = null, 12 | 13 | @SerializedName("size") 14 | val size: Int? = null, 15 | 16 | @SerializedName("ip") 17 | val ip: String? = null, 18 | 19 | @SerializedName("width") 20 | val width: Int? = null, 21 | 22 | @SerializedName("storename") 23 | val storename: String? = null, 24 | 25 | @SerializedName("delete") 26 | val delete: String? = null, 27 | 28 | @SerializedName("hash") 29 | val hash: String? = null, 30 | 31 | @SerializedName("url") 32 | val url: String? = null, 33 | 34 | @SerializedName("height") 35 | val height: Int? = null, 36 | 37 | @SerializedName("timestamp") 38 | val timestamp: Int? = null, 39 | val msg: String? = null 40 | ) -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/model/NotificationModel.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.model 2 | 3 | import android.os.Parcelable 4 | import im.fdx.v2ex.ui.main.Topic 5 | import im.fdx.v2ex.ui.member.Member 6 | import kotlinx.parcelize.Parcelize 7 | 8 | /** 9 | * Created by fdx on 2017/3/24. 10 | */ 11 | 12 | @Parcelize 13 | class NotificationModel(var time: String? = "", 14 | var replyPosition: String? = "", 15 | var type: String? = "", 16 | var topic: Topic? = Topic(), 17 | var member: Member? = Member(), 18 | var content: String? = "", 19 | var id: String? = "") : Parcelable, VModel() 20 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/model/Res.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Res( 6 | 7 | @field:SerializedName("code") 8 | val code: String? = null, 9 | 10 | @field:SerializedName("data") 11 | val data: Data? = null 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/model/VModel.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.model 2 | 3 | 4 | /** 5 | * used for proguard 6 | */ 7 | abstract class VModel -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/network/GetMoreRepliesWorker.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.network 2 | 3 | import android.app.* 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Build 7 | import androidx.core.app.NotificationCompat 8 | import androidx.core.content.ContextCompat 9 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 10 | import androidx.work.Worker 11 | import androidx.work.WorkerParameters 12 | import com.elvishew.xlog.XLog 13 | import im.fdx.v2ex.R 14 | import im.fdx.v2ex.ui.NotificationActivity 15 | import im.fdx.v2ex.utils.Keys 16 | import im.fdx.v2ex.utils.extensions.logd 17 | import okhttp3.Call 18 | import okhttp3.Callback 19 | import okhttp3.Response 20 | import java.io.IOException 21 | 22 | class GetMoreRepliesWorker(val context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) { 23 | override fun doWork(): Result { 24 | 25 | getAllMore() 26 | 27 | return Result.success() 28 | 29 | } 30 | private fun getAllMore() { 31 | val totalPage = inputData.getInt("page", -1) 32 | val topicId = inputData.getString("topic_id") 33 | val isToBottom = inputData.getBoolean("bottom", false) 34 | 35 | logd("$totalPage | $topicId") 36 | if (totalPage <= 1 || topicId == null) { 37 | return 38 | } 39 | 40 | try { 41 | for (i in 2..totalPage) { 42 | val response = vCall("${NetManager.HTTPS_V2EX_BASE}/t/$topicId?p=$i").execute() 43 | val parser = Parser(response.body!!.string()) 44 | val replies = parser.getReplies() 45 | 46 | if (replies.isEmpty()) return 47 | val replyIntent = Intent() 48 | replyIntent.action = Keys.ACTION_GET_MORE_REPLY 49 | replyIntent.putExtra(Keys.KEY_TOPIC_ID, topicId) 50 | replyIntent.putParcelableArrayListExtra("replies", replies) 51 | 52 | if (i == totalPage && isToBottom) { 53 | replyIntent.putExtra("bottom", true) 54 | } 55 | LocalBroadcastManager.getInstance(context).sendBroadcast(replyIntent) 56 | } 57 | 58 | } catch (e: IOException) { 59 | e.printStackTrace() 60 | } 61 | 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/network/cookie/CookiePersistor.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.network.cookie 2 | 3 | import okhttp3.Cookie 4 | 5 | /** 6 | * Created by fdx on 2017/3/16. 7 | */ 8 | 9 | interface CookiePersistor { 10 | 11 | fun removeAll(cookies: List) 12 | 13 | fun clear() 14 | 15 | fun persist(cookie: Cookie) 16 | 17 | fun persistAll(cookies: Collection) 18 | 19 | fun loadAll(): List 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/network/cookie/MyCookieJar.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.network.cookie 2 | 3 | import okhttp3.Cookie 4 | import okhttp3.CookieJar 5 | import okhttp3.HttpUrl 6 | import java.util.* 7 | 8 | /** 9 | * Created by fdx on 2017/3/16. 10 | * 11 | * 12 | * 请用自己的Cookie策略,而不是开源库。 13 | */ 14 | class MyCookieJar(private val cookiePersistor: SharedPrefsPersistor) : CookieJar { 15 | private val cookieStore = HashMap>() 16 | private val mCookies = ArrayList() 17 | 18 | override fun saveFromResponse(url: HttpUrl, cookies: List) { 19 | mCookies.clear() 20 | mCookies.addAll(cookies) 21 | cookieStore[url.host] = mCookies 22 | cookiePersistor.persistAll(cookies) 23 | } 24 | 25 | override fun loadForRequest(url: HttpUrl): List { 26 | 27 | val cookies: List? = if (cookiePersistor.loadByHost(url.host).isNotEmpty()) { 28 | cookiePersistor.loadByHost(url.host.removePrefix("www.")) 29 | } else 30 | cookieStore[url.host] 31 | return cookies ?: ArrayList() 32 | } 33 | 34 | fun clear() { 35 | mCookies.clear() 36 | cookieStore.clear() 37 | cookiePersistor.clear() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | /** 6 | * Create by fandongxiao on 2019/5/16 7 | */ 8 | open class BaseFragment : Fragment() { 9 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/MyCallback.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui 2 | 3 | import androidx.recyclerview.widget.ListUpdateCallback 4 | import im.fdx.v2ex.ui.main.TopicsRVAdapter 5 | 6 | 7 | /** 8 | * Created by fdx on 2017/7/25. 9 | * 10 | */ 11 | internal class MyCallback(var adapter: TopicsRVAdapter) : ListUpdateCallback { 12 | var firstInsert = -1 13 | 14 | override fun onChanged(position: Int, count: Int, payload: Any?) { 15 | adapter.notifyItemRangeChanged(position, count, payload) 16 | } 17 | 18 | override fun onInserted(position: Int, count: Int) { 19 | if (firstInsert == -1 || firstInsert > position) { 20 | firstInsert = position 21 | } 22 | adapter.notifyItemRangeInserted(position, count) 23 | } 24 | 25 | override fun onMoved(fromPosition: Int, toPosition: Int) { 26 | adapter.notifyItemMoved(fromPosition, toPosition) 27 | } 28 | 29 | override fun onRemoved(position: Int, count: Int) { 30 | adapter.notifyItemRangeRemoved(position, count) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/TestActivity.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui 2 | 3 | import android.os.Bundle 4 | import android.os.PersistableBundle 5 | import android.telephony.PhoneNumberFormattingTextWatcher 6 | import android.telephony.PhoneNumberUtils 7 | import im.fdx.v2ex.R 8 | import im.fdx.v2ex.utils.extensions.logi 9 | import im.fdx.v2ex.utils.extensions.toast 10 | import java.util.* 11 | 12 | class TestActivity : BaseActivity() { 13 | 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | 18 | setContentView(R.layout.activity_test) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/favor/FavorActivity.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.favor 2 | 3 | import android.os.Bundle 4 | import android.view.MotionEvent 5 | import androidx.viewpager.widget.ViewPager 6 | import androidx.viewpager2.widget.ViewPager2 7 | import com.google.android.material.tabs.TabLayout 8 | import com.google.android.material.tabs.TabLayoutMediator 9 | import im.fdx.v2ex.R 10 | import im.fdx.v2ex.ui.BaseActivity 11 | import im.fdx.v2ex.ui.favor.FavorViewPagerAdapter.Companion.titles 12 | import im.fdx.v2ex.ui.tabTitles 13 | import im.fdx.v2ex.utils.extensions.setUpToolbar 14 | import im.fdx.v2ex.view.ViewPagerHelper 15 | import kotlin.math.abs 16 | 17 | class FavorActivity : BaseActivity() { 18 | private var helper: ViewPagerHelper? = null 19 | lateinit var viewPager: ViewPager2 20 | lateinit var tabLayout: TabLayout 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_follow_activity) 24 | 25 | setUpToolbar(getString(R.string.my_follow)) 26 | 27 | 28 | tabLayout = findViewById(R.id.tl_favor) 29 | viewPager = findViewById(R.id.viewpager_follow) 30 | viewPager.offscreenPageLimit = titles.size 31 | viewPager.adapter = FavorViewPagerAdapter(this) 32 | TabLayoutMediator(tabLayout, viewPager) { tab, position -> 33 | tab.text = titles[position] 34 | }.attach() 35 | 36 | helper = ViewPagerHelper(viewPager) 37 | 38 | } 39 | 40 | 41 | override fun dispatchTouchEvent(ev: MotionEvent): Boolean { 42 | helper?.dispatchTouchEvent(ev) 43 | return super.dispatchTouchEvent(ev) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/favor/FavorViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.favor 2 | 3 | import android.os.Bundle 4 | import androidx.core.os.bundleOf 5 | import androidx.fragment.app.Fragment 6 | import androidx.fragment.app.FragmentActivity 7 | import androidx.viewpager.widget.PagerAdapter 8 | import androidx.viewpager2.adapter.FragmentStateAdapter 9 | import im.fdx.v2ex.ui.main.TopicsFragment 10 | import im.fdx.v2ex.utils.Keys 11 | 12 | /** 13 | * Created by fdx on 2017/4/13. 14 | */ 15 | internal class FavorViewPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { 16 | 17 | companion object { 18 | val titles = arrayOf("节点收藏", "主题收藏", "特别关注") 19 | } 20 | 21 | override fun getItemCount(): Int { 22 | return titles.size 23 | } 24 | 25 | override fun createFragment(position: Int): Fragment { 26 | return when (position) { 27 | 0 -> NodeFavorFragment() 28 | 1 -> { 29 | TopicsFragment().apply { 30 | arguments = bundleOf(Keys.FAVOR_FRAGMENT_TYPE to position) 31 | } 32 | } 33 | 2 -> { 34 | TopicsFragment().apply { 35 | arguments = bundleOf(Keys.FAVOR_FRAGMENT_TYPE to position) 36 | } 37 | } 38 | else -> throw Exception() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/main/Comment.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.main 2 | 3 | import android.os.Parcelable 4 | import im.fdx.v2ex.model.VModel 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class Comment(var title: String = "", 9 | var created: Long = 0, 10 | var createdOriginal: String = "", 11 | var content: String = "") : Parcelable, VModel() -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/main/MyDiffCallback.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.main 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | /** 6 | * Created by fdx on 2017/7/11. 7 | * fdx will maintain it 8 | */ 9 | class MyDiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() { 10 | 11 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 12 | return oldList[oldItemPosition].id == newList[newItemPosition].id 13 | } 14 | 15 | override fun getOldListSize() = oldList.size 16 | override fun getNewListSize() = newList.size 17 | 18 | /** 19 | * 有点问题,GoodText不加载图片 20 | */ 21 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 22 | return oldList[oldItemPosition].replies == newList[newItemPosition].replies && 23 | oldList[oldItemPosition].content_rendered == newList[newItemPosition].content_rendered 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/main/MyViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.main 2 | 3 | import androidx.core.os.bundleOf 4 | import androidx.fragment.app.Fragment 5 | import androidx.fragment.app.FragmentActivity 6 | import androidx.viewpager2.adapter.FragmentStateAdapter 7 | import com.google.gson.Gson 8 | import com.google.gson.reflect.TypeToken 9 | import im.fdx.v2ex.myApp 10 | import im.fdx.v2ex.pref 11 | import im.fdx.v2ex.ui.MyTab 12 | import im.fdx.v2ex.ui.tabPaths 13 | import im.fdx.v2ex.ui.tabTitles 14 | import im.fdx.v2ex.utils.Keys 15 | import im.fdx.v2ex.utils.Keys.PREF_TAB 16 | import java.util.* 17 | 18 | 19 | /** 20 | * Created by fdx on 2015/10/15. 21 | * 从MainActivity分离出来. 用了FragmentStatePagerAdapter 替代FragmentPagerAdapter,才可以动态切换Fragment 22 | * 弃用了Volley 和 模拟web + okhttp 23 | * 24 | * todo pageadapter有更新,明天需要完成 25 | */ 26 | internal class MyViewPagerAdapter( 27 | fa: FragmentActivity) : FragmentStateAdapter(fa) { 28 | 29 | private val mFragments = ArrayList() 30 | val myTabList = mutableListOf() 31 | init { 32 | initFragment() 33 | } 34 | 35 | fun initFragment() { 36 | myTabList.clear() 37 | mFragments.clear() 38 | 39 | var jsonData = pref.getString(PREF_TAB, null) 40 | if (jsonData == null) { 41 | val list = MutableList(tabTitles.size) { index: Int -> 42 | MyTab(tabTitles[index], tabPaths[index]) 43 | } 44 | 45 | jsonData = Gson().toJson(list) 46 | } 47 | 48 | val turnsType = object : TypeToken>() {}.type 49 | val list = Gson().fromJson>(jsonData, turnsType) 50 | 51 | if(list.isNullOrEmpty()) { //可能的一些奇怪的问题 52 | MutableList(tabTitles.size) { index: Int -> 53 | MyTab(tabTitles[index], tabPaths[index]) 54 | } 55 | } 56 | 57 | for (it in list) { 58 | if (!myApp.isLogin && it.path == "recent") { 59 | continue 60 | } 61 | mFragments.add(TopicsFragment().apply { arguments = bundleOf(Keys.KEY_TAB to it.path, Keys.KEY_TYPE to it.type) }) 62 | myTabList.add(it) 63 | } 64 | } 65 | 66 | override fun getItemCount(): Int { 67 | return myTabList.size + 1 68 | } 69 | 70 | override fun createFragment(position: Int): Fragment { 71 | return if (position < itemCount -1) 72 | mFragments[position] 73 | else 74 | Fragment() 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/main/TopicDao.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.main 2 | 3 | import androidx.room.* 4 | 5 | 6 | @Dao 7 | interface TopicDao { 8 | 9 | // @Insert(onConflict = OnConflictStrategy.REPLACE) 10 | // fun insertTopic(vararg topics: Topic) 11 | // 12 | // @Insert 13 | // fun insertBothTopic(topic1: Topic, topic2: Topic) 14 | // 15 | // @Update 16 | // fun updateTopic(vararg topic: Topic) 17 | // 18 | // @Delete 19 | // fun deleteTopic(vararg topic: Topic) 20 | // 21 | // 22 | // @Query("SELECT * FROM topic WHERE replies > :replyNum") 23 | // fun loadAllUsersOlderThan(replyNum: Int): List 24 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/main/model/SearchResult.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.main.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class SearchResult( 6 | 7 | @SerializedName("hits") 8 | val hits: List? = null, 9 | 10 | @SerializedName("took") 11 | val took: Int? = null, 12 | 13 | @SerializedName("total") 14 | val total: Int? = null, 15 | 16 | @SerializedName("timed_out") 17 | val timedOut: Boolean? = null 18 | ) 19 | 20 | data class HitsItem( 21 | 22 | @SerializedName("highlight") 23 | val highlight: Highlight? = null, 24 | 25 | @SerializedName("_index") 26 | val index: String? = null, 27 | 28 | @SerializedName("_type") 29 | val type: String? = null, 30 | 31 | @SerializedName("_source") 32 | val source: Source? = null, 33 | 34 | @SerializedName("_id") 35 | val id: String? = null, 36 | 37 | @SerializedName("_score") 38 | val score: Double? = null 39 | ) 40 | 41 | data class Source( 42 | 43 | @SerializedName("node") 44 | val node: Int? = null, 45 | 46 | @SerializedName("replies") 47 | val replies: Int? = null, 48 | 49 | @SerializedName("created") 50 | val created: String? = null, 51 | 52 | @SerializedName("member") 53 | val member: String? = null, 54 | 55 | @SerializedName("id") 56 | val id: Int? = null, 57 | 58 | @SerializedName("title") 59 | val title: String? = null, 60 | 61 | @SerializedName("content") 62 | val content: String? = null 63 | ) 64 | data class Highlight( 65 | 66 | @SerializedName("reply_list.content") 67 | val replyListContent: List? = null, 68 | 69 | @SerializedName("title") 70 | val title: List? = null, 71 | 72 | @SerializedName("postscript_list.content") 73 | val postscriptListContent: List? = null, 74 | 75 | @SerializedName("content") 76 | val content: List? = null 77 | ) -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/member/DiffReply.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.member 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | /** 6 | * Created by fdx on 2017/7/31. 7 | * 8 | */ 9 | class DiffReply(val oldList: List, val newList: List) : DiffUtil.Callback() { 10 | 11 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) 12 | = oldList[oldItemPosition].content == newList[newItemPosition].content 13 | 14 | override fun getOldListSize() = oldList.size 15 | override fun getNewListSize() = newList.size 16 | 17 | /** 18 | * 有点问题,GoodText不加载图片 19 | */ 20 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) 21 | = oldList[oldItemPosition].content == newList[newItemPosition].content 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/member/Member.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.member 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import kotlinx.parcelize.Parcelize 6 | 7 | /** 8 | * Created by a708 on 16-1-16. 9 | * V2ex 的 个人信息 模型 10 | 11 | * username 和 id 都是 key value 12 | */ 13 | 14 | //{ 15 | // "status" : "found", 16 | // "id" : 32044, 17 | // "url" : "http://www.v2ex.com/member/cxshun", 18 | // "username" : "cxshun", 19 | // "website" : "http://www.chenxiaoshun.com/", 20 | // "twitter" : "cxshun", 21 | // "psn" : "", 22 | // "github" : "cxshun", 23 | // "btc" : "", 24 | // "location" : "", 25 | // "tagline" : "", 26 | // "bio" : "", 27 | // "avatar_mini" : "//cdn.v2ex.com/gravatar/273313b23fdf59a7695f46c6ae175776?s=24&d=retro, 28 | // 部分新域名, 部分旧域名 cdn.v2ex.com/avatar/00a6/7ce3/279733_mini.png?m=1546517640 29 | // "avatar_normal" : "//cdn.v2ex.com/gravatar/273313b23fdf59a7695f46c6ae175776?s=24&d=retro", 30 | // 有坑,现在都是返回小图。 31 | // "avatar_large" : "//cdn.v2ex.com/gravatar/273313b23fdf59a7695f46c6ae175776?s=24&d=retro", 32 | // "created" : 1357733451 33 | // } 34 | 35 | @Parcelize 36 | data class Member( 37 | @ColumnInfo(name = "member_id") 38 | var id: String = "", 39 | var username: String = "", 40 | var tagline: String? = "", 41 | @ColumnInfo(name = "member_created") 42 | var created: String = "", 43 | @ColumnInfo(name = "member_avatar_normal") 44 | var avatar_normal: String = "", 45 | var bio: String? = "", 46 | var github: String? = "", 47 | var btc: String? = "", 48 | var location: String? = "", 49 | var twitter: String? = "", 50 | var website: String? = "" 51 | ) : Parcelable { 52 | 53 | val avatarNormalUrl: String? //为了减少在sov2ex 的页面的报错,因为他的接口不返回头像 54 | get() { 55 | if(avatar_normal == "") return null 56 | 57 | val prefix = if (avatar_normal.startsWith("https:")) "" else "https:" 58 | return prefix + Regex("\\?s=\\d{1,3}").replace(avatar_normal, "?s=64") 59 | } 60 | 61 | val avatarLargeUrl: String 62 | get() { 63 | val prefix = if (avatar_normal.startsWith("https:")) "" else "https:" 64 | return prefix + Regex("\\?s=\\d{1,3}").replace(avatar_normal, "?s=128").replace("normal","large").replace("mini","large") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/member/MemberReplyModel.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.member 2 | 3 | import android.os.Parcelable 4 | import im.fdx.v2ex.ui.main.Topic 5 | import kotlinx.parcelize.Parcelize 6 | 7 | /** 8 | * Created by fdx on 2017/7/16. 9 | * fdx will maintain it 10 | */ 11 | @Parcelize 12 | data class MemberReplyModel(var id: String? = "", 13 | var topic: Topic = Topic(), 14 | var content: String? = null, 15 | var createdOriginal: String = "", 16 | var create: Long = 0L) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/member/ReplyAdapter.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.member 2 | 3 | import android.app.Activity 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.DiffUtil 9 | import im.fdx.v2ex.R 10 | import im.fdx.v2ex.ui.topic.TopicActivity 11 | import im.fdx.v2ex.utils.Keys 12 | import im.fdx.v2ex.utils.TimeUtil 13 | import im.fdx.v2ex.view.GoodTextView 14 | import im.fdx.v2ex.utils.extensions.startActivity 15 | 16 | /** 17 | * Created by fdx on 2017/7/15. 18 | * fdx will maintain it 19 | * 在用户信息的回复页面 20 | */ 21 | class ReplyAdapter(val activity: Activity, 22 | var list: MutableList = mutableListOf()) 23 | : androidx.recyclerview.widget.RecyclerView.Adapter() { 24 | 25 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 26 | ReplyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_reply_member, parent, false)) 27 | 28 | override fun onBindViewHolder(holder: ReplyViewHolder, position: Int) { 29 | 30 | val reply = list[position] 31 | holder.tvTitle.text =reply.createdOriginal + " 回复了主题:\n" + reply.topic.title 32 | holder.tvContent.setGoodText(reply.content, true) 33 | // holder.tvTime.text = reply.createdOriginal 34 | 35 | holder.itemView.setOnClickListener { 36 | activity.startActivity(Keys.KEY_TOPIC_ID to reply.topic.id) 37 | } 38 | } 39 | 40 | 41 | fun updateItem(newItems: List) { 42 | // val diffResult = DiffUtil.calculateDiff(DiffReply(list, newItems)) 43 | list.clear() 44 | list.addAll(newItems) 45 | notifyDataSetChanged() 46 | // diffResult.dispatchUpdatesTo(this) 47 | 48 | } 49 | 50 | fun addItems(newItems: List) { 51 | val old = list.toList() 52 | list.addAll(newItems) 53 | val diffResult = DiffUtil.calculateDiff(DiffReply(old, list)) 54 | diffResult.dispatchUpdatesTo(this) 55 | } 56 | 57 | override fun getItemCount() = list.size 58 | 59 | inner class ReplyViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) { 60 | val tvTitle: TextView = itemView.findViewById(R.id.tv_title) 61 | val tvContent: GoodTextView = itemView.findViewById(R.id.tv_content_reply) 62 | val tvTime: TextView = itemView.findViewById(R.id.tv_create) 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/node/AllNodesAdapter.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.node 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import com.elvishew.xlog.XLog 9 | import im.fdx.v2ex.R 10 | import im.fdx.v2ex.utils.Keys 11 | import im.fdx.v2ex.utils.extensions.load 12 | import im.fdx.v2ex.utils.extensions.startActivity 13 | import java.util.* 14 | 15 | /** 16 | * Created by fdx on 2016/9/13. 17 | * 简单节点adapter,用在个人favor 18 | */ 19 | class AllNodesAdapter(val isShowImg: Boolean = false) : androidx.recyclerview.widget.RecyclerView.Adapter() { 20 | 21 | private var mNodes: MutableList = ArrayList() 22 | private var realAllNodes: MutableList = ArrayList() 23 | 24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 25 | AllNodeViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_all_nodes, parent, false)) 26 | 27 | override fun onBindViewHolder(holder: AllNodeViewHolder, position: Int) { 28 | XLog.tag("RV_INNER").e("$position") 29 | val node = mNodes[position] 30 | 31 | if (isShowImg) holder.ivNodeIcon.load(node.avatarLargeUrl) 32 | else { 33 | holder.ivNodeIcon.visibility = View.GONE 34 | } 35 | holder.tvNodeName.text = node.title 36 | holder.itemView.setOnClickListener { 37 | it.context.startActivity(Keys.KEY_NODE_NAME to node.name) 38 | } 39 | 40 | } 41 | 42 | override fun getItemCount() = mNodes.size 43 | 44 | fun setAllData(nodes: MutableList) { 45 | mNodes = nodes 46 | realAllNodes = nodes 47 | } 48 | 49 | fun addAll(nodes: List) { 50 | mNodes.addAll(nodes) 51 | } 52 | 53 | fun clear() = mNodes.clear() 54 | 55 | fun filter(newText: String) { 56 | 57 | if (newText.isEmpty()) { 58 | mNodes = realAllNodes 59 | notifyDataSetChanged() 60 | return 61 | } 62 | 63 | mNodes = realAllNodes.filter { 64 | it.name.contains(newText) || it.title.contains(newText) || it.title_alternative.contains(newText) 65 | }.toMutableList() 66 | notifyDataSetChanged() 67 | } 68 | 69 | class AllNodeViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) { 70 | var tvNodeName: TextView = itemView.findViewById(R.id.tv_node_name) 71 | var ivNodeIcon: ImageView = itemView.findViewById(R.id.iv_node_image) 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/node/Node.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.node 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import kotlinx.parcelize.Parcelize 8 | 9 | /** 10 | * Created by a708 on 16-1-17. 11 | * V2ex 节点模型 12 | * 其中 name 是 key value 13 | */ 14 | 15 | ///api/topics/show.json 16 | // 17 | // 参数(选其一) 18 | // username 根据用户名取该用户所发表主题 19 | // node_id 根据节点id取该节点下所有主题 20 | // node_name 根据节点名取该节点下所有主题 21 | 22 | //------------------ 23 | //{ 24 | // "id" : 90, 25 | // "name" : "python", 26 | // "url" : "http://www.v2ex.com/go/python", 27 | // "title" : "Python", 28 | // "title_alternative" : "Python", 29 | // "topics" : 4272, 30 | // "stars" : 3234, 31 | // 32 | // "header" : "这里讨论各种 Python 语言编程话题,也包括 Django,Tornado 等框架的讨论。这里是一个能够帮助你解决实际问题的地方。", 33 | // 34 | // 35 | // "footer" : null, 36 | // 37 | // "created" : 1278683336, 38 | // "avatar_mini" : "//cdn.v2ex.co/navatar/8613/985e/90_mini.png?m=1452823690", 39 | // "avatar_normal" : "//cdn.v2ex.co/navatar/8613/985e/90_normal.png?m=1452823690", 40 | // "avatar_large" : "//cdn.v2ex.co/navatar/8613/985e/90_large.png?m=1452823690" 41 | // } 42 | 43 | @Entity 44 | @Parcelize 45 | data class Node( 46 | @PrimaryKey 47 | @ColumnInfo(name = "node_id") 48 | var id: String = "", 49 | @ColumnInfo(name = "node_name") 50 | var name: String = "", //英文名称 51 | @ColumnInfo(name = "node_url") 52 | var url: String = "", 53 | @ColumnInfo(name = "node_title") 54 | var title: String = "", //展示名称 55 | var title_alternative: String = "", 56 | var topics: Int = 0, 57 | var stars: Int = 0, 58 | @ColumnInfo(name = "node_created") 59 | var created: Long = 0, 60 | @ColumnInfo(name = "node_avatar_normal") 61 | var avatar_normal: String? = "", 62 | var header: String? = "", 63 | var category: String? = "") : Parcelable { 64 | 65 | 66 | val avatarNormalUrl: String 67 | get() = "https:" + Regex("\\?s=\\d{1,3}").replace(avatar_normal?:"", "?s=64") 68 | 69 | val avatarLargeUrl: String 70 | get() = when { 71 | avatar_normal?.startsWith("/static") == true -> "https://www.v2ex.com/static/img/node_large.png" 72 | else -> Regex("\\?s=\\d{1,3}").replace(avatar_normal?:"", "?s=128").replace("normal","large").replace("mini","large") 73 | } 74 | 75 | override fun toString() = "$title / $name" 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/node/NodeDao.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.node 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | 8 | @Dao 9 | interface NodeDao { 10 | 11 | 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | fun insertNode(vararg nodes: Node) 14 | 15 | @Query("select * from node ") 16 | fun getNodes(): List 17 | 18 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/node/SimpleNodesTextAdapter.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.node 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import im.fdx.v2ex.R 8 | 9 | class SimpleNodesTextAdapter(private var mNodes: MutableList, private val action: (Node) -> Unit) 10 | : androidx.recyclerview.widget.RecyclerView.Adapter() { 11 | 12 | override fun onBindViewHolder(holder: SimpleVH, position: Int) { 13 | holder.tvTitle.text = mNodes[position].title 14 | holder.tvTitle.setOnClickListener { 15 | action(mNodes[position]) 16 | } 17 | } 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 20 | SimpleVH(LayoutInflater.from(parent.context).inflate(R.layout.item_node_simple, parent, false)) 21 | 22 | override fun getItemCount() = mNodes.size 23 | 24 | inner class SimpleVH(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) { 25 | val tvTitle = itemView as TextView 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/topic/BottomReplyList.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.topic 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 10 | import im.fdx.v2ex.R 11 | import im.fdx.v2ex.databinding.ItemReplyViewBinding 12 | 13 | internal class BottomReplyList(var list: List) : BottomSheetDialogFragment(){ 14 | 15 | 16 | companion object { 17 | fun newInstance(list: List): BottomReplyList { 18 | return BottomReplyList(list) 19 | } 20 | } 21 | 22 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 23 | 24 | return inflater.inflate(R.layout.bottom_list_sheet, container, false) 25 | } 26 | 27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 28 | super.onViewCreated(view, savedInstanceState) 29 | val rv = view.findViewById(R.id.rv) 30 | rv.layoutManager = LinearLayoutManager(rv.context) 31 | rv.adapter = RAdapter(list) 32 | } 33 | 34 | inner class RAdapter(var list: List) : RecyclerView.Adapter() { 35 | override fun getItemCount(): Int { 36 | return list.size 37 | } 38 | 39 | override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { 40 | holder.bind(list[position]) 41 | } 42 | 43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { 44 | return ItemViewHolder(ItemReplyViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)) 45 | } 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/topic/MyReply.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.topic 2 | 3 | import androidx.room.* 4 | 5 | @Entity(tableName = "my_reply") 6 | data class MyReply( 7 | 8 | @PrimaryKey 9 | @ColumnInfo(name = "topic_id") 10 | var topicId :String, 11 | @ColumnInfo(name = "content") 12 | var content : String 13 | ) 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/topic/MyReplyDao.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.topic 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | 8 | @Dao 9 | interface MyReplyDao { 10 | 11 | @Query("SELECT * FROM my_reply WHERE topic_id = (:topicId)") 12 | suspend fun getMyReplyById(topicId: String) : MyReply? 13 | 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | suspend fun insert(myReply: MyReply): Long 17 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/ui/topic/Reply.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.ui.topic 2 | 3 | import android.os.Parcelable 4 | import im.fdx.v2ex.ui.member.Member 5 | import kotlinx.parcelize.Parcelize 6 | 7 | /** 8 | * Created by a708 on 15-9-8. 9 | * 评论模型,用于传递从JSON获取到的数据。 10 | * 以后将加入添加评论功能。 11 | */ 12 | 13 | //{ 14 | // "id" : 2826846, 15 | // "thanks" : 0, 16 | // "content" : "关键你是男还是女?", 17 | // "content_rendered" : "关键你是男还是女?", 18 | // "member" : { 19 | // "id" : 27619, 20 | // "username" : "hengzhang", 21 | // "tagline" : "我白天是个民工,晚上就是个有抱负的IT人士。", 22 | // "avatar_mini" : "//cdn.v2ex.co/avatar/d165/7a2a/27619_mini.png?m=1413707431", 23 | // "avatar_normal" : "//cdn.v2ex.co/avatar/d165/7a2a/27619_normal.png?m=1413707431", 24 | // "avatar_large" : "//cdn.v2ex.co/avatar/d165/7a2a/27619_large.png?m=1413707431" 25 | // }, 26 | // "created" : 1453030169, 27 | // "last_modified" : 1453030169 28 | // } 29 | 30 | @Parcelize 31 | data class Reply(var id: String = "", 32 | var content: String = "", 33 | var content_rendered: String = "", 34 | var thanks: Int = 0, 35 | var created: Long = 0, 36 | var createdOriginal: String= "", 37 | var isThanked: Boolean = false, 38 | var member: Member? = null, 39 | var isLouzu: Boolean = false, 40 | var showTime: String = "", 41 | private var rowNum: Int = -1, 42 | ) : Parcelable { 43 | 44 | fun setRowNum(num: Int) { 45 | rowNum = num 46 | } 47 | fun getRowNum(base:Int = 0) : Int = if (rowNum <= 0) base else rowNum 48 | 49 | override fun toString() = "Reply{content='$content_rendered}" 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/utils/EndlessOnScrollListener.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils 2 | 3 | import androidx.recyclerview.widget.LinearLayoutManager 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.elvishew.xlog.XLog 6 | import im.fdx.v2ex.utils.extensions.logd 7 | 8 | abstract class EndlessOnScrollListener(val rvReply: RecyclerView, val mLinearLayoutManager:LinearLayoutManager) : RecyclerView.OnScrollListener() { 9 | 10 | private val visibleThreshold = 2 11 | 12 | private var pageToLoad = 1 13 | 14 | var loading = false 15 | var totalPage = 0 16 | 17 | private var pageAfterLoaded = pageToLoad 18 | 19 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 20 | XLog.d("onScrolled + $dy") 21 | if (dy <= 0) { 22 | return 23 | } 24 | val visibleItemCount = recyclerView.childCount 25 | val totalItemCount = mLinearLayoutManager.itemCount 26 | val firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition() 27 | val lastVisibleItem = mLinearLayoutManager.findLastCompletelyVisibleItemPosition() 28 | 29 | synchronized(this) { 30 | 31 | logd("$totalItemCount , $lastVisibleItem , $pageToLoad") 32 | if (lastVisibleItem == totalItemCount - 1) { 33 | rvReply.stopScroll() 34 | if (pageAfterLoaded == totalPage) { 35 | onCompleted() 36 | } 37 | } 38 | 39 | if (pageToLoad < totalPage && !loading && totalItemCount - visibleItemCount <= firstVisibleItem + visibleThreshold) { 40 | // End has been reached, Do something 41 | pageToLoad++ 42 | onLoadMore(pageToLoad) 43 | loading = true 44 | } 45 | } 46 | } 47 | 48 | fun restart() { 49 | pageToLoad = 1 50 | } 51 | 52 | fun isRestart() :Boolean { 53 | return pageToLoad == 1 54 | } 55 | 56 | fun success() { 57 | pageAfterLoaded = pageToLoad 58 | } 59 | 60 | abstract fun onLoadMore(currentPage: Int) 61 | abstract fun onCompleted() 62 | 63 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/utils/ViewUtil.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils 2 | 3 | import android.content.res.Resources 4 | 5 | 6 | /** 7 | * Created by fdx on 2017/4/29. 8 | * 9 | * 10 | * pixel 和 dp 转换 11 | */ 12 | 13 | object ViewUtil { 14 | val screenHeight: Int 15 | get() { 16 | val metrics = Resources.getSystem().displayMetrics 17 | 18 | return metrics.heightPixels 19 | } 20 | 21 | val screenWidth : Int 22 | get() { 23 | 24 | return Resources.getSystem().displayMetrics.widthPixels 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/utils/extensions/DpExt.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils.extensions 2 | 3 | import android.content.res.Resources 4 | import kotlin.math.roundToInt 5 | 6 | 7 | /** 8 | * Created by fdx on 2017/6/8. 9 | * fdx will maintain it 10 | */ 11 | 12 | 13 | fun Int.dp2px(): Int { 14 | val metrics = Resources.getSystem().displayMetrics 15 | val px = this * 1.0f * (metrics.densityDpi / 160f) 16 | return px.roundToInt() 17 | } 18 | 19 | fun Int.px2dp(): Int { 20 | val metrics = Resources.getSystem().displayMetrics 21 | val dp = this * 1.0f / (metrics.densityDpi / 160f) 22 | return dp.roundToInt() 23 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/utils/extensions/StringExt.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils.extensions 2 | 3 | import java.net.URLDecoder 4 | 5 | /** 6 | * Created by fdx on 2017/7/3. 7 | * fdx will maintain it 8 | */ 9 | 10 | 11 | /** 12 | * 将链接转成完整链接,一般用 GoodTextView 13 | */ 14 | fun String.fullUrl() = this.replace("href=\"/member/", "href=\"https://www.v2ex.com/member/") 15 | .replace("href=\"/i/", "href=\"https://i.v2ex.co/") 16 | .replace("href=\"/t/", "href=\"https://www.v2ex.com/t/") 17 | .replace("href=\"/go/", "href=\"https://www.v2ex.com/go/") 18 | .replace("Unit): BottomSheetMenu { 43 | val view = LayoutInflater.from(activity).inflate(R.layout.simple_list_item_center, null, false) as TextView 44 | view.text = title 45 | view.setOnClickListener{ 46 | action() 47 | bottomSheet.dismiss() 48 | } 49 | val p = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) 50 | container.addView(view, p) 51 | return this 52 | } 53 | 54 | fun addDivider() : BottomSheetMenu { 55 | val view = View(activity) 56 | view.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, 1) 57 | view.setPadding(0, 6, 0, 6) 58 | view.setBackgroundColor(ContextCompat.getColor(activity, R.color.divider_color)) 59 | container.addView(view) 60 | return this 61 | } 62 | 63 | fun addItems(titles: List, action: (Int,String) -> Unit): BottomSheetMenu { 64 | titles.forEachIndexed { index, s -> 65 | addItem(s) { 66 | action(index, s) 67 | } 68 | } 69 | return this 70 | } 71 | 72 | 73 | fun show() { 74 | if(!activity.isDestroyed) { 75 | bottomSheet.show() 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/view/CustomChrome.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.view 2 | 3 | import android.content.ActivityNotFoundException 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import androidx.browser.customtabs.CustomTabColorSchemeParams 8 | import androidx.browser.customtabs.CustomTabsIntent 9 | import androidx.core.content.ContextCompat 10 | import androidx.core.content.ContextCompat.startActivity 11 | import im.fdx.v2ex.R 12 | 13 | 14 | /** 15 | * Created by fdx on 2017/3/23. 16 | * 无法设置文字颜色 17 | */ 18 | class CustomChrome(private val context: Context) { 19 | private val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder() 20 | 21 | init { 22 | builder.setShowTitle(true) 23 | builder.setDefaultColorSchemeParams( 24 | CustomTabColorSchemeParams.Builder() 25 | .setToolbarColor(ContextCompat.getColor(context, R.color.chrome_tab)).build() 26 | ) 27 | builder.setShareState(CustomTabsIntent.SHARE_STATE_ON) 28 | } 29 | 30 | fun load(url: String) { 31 | 32 | val customTabsIntent = builder.build() 33 | try { 34 | customTabsIntent.launchUrl(context, Uri.parse(url)) 35 | } catch (e: ActivityNotFoundException) { 36 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) 37 | context.startActivity(intent) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/view/NaviItem.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.view 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.util.AttributeSet 6 | import android.widget.FrameLayout 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import im.fdx.v2ex.R 10 | 11 | /** 12 | * TODO: document your custom view class. 13 | */ 14 | class NaviItem : FrameLayout { 15 | /** 16 | * In the example view, this drawable is drawn above the text. 17 | */ 18 | var exampleDrawable: Drawable? = null 19 | 20 | constructor(context: Context) : super(context) { 21 | init(null, 0) 22 | } 23 | 24 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 25 | init(attrs, 0) 26 | } 27 | 28 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( 29 | context, 30 | attrs, 31 | defStyle 32 | ) { 33 | init(attrs, defStyle) 34 | } 35 | 36 | var icon: Drawable? = null 37 | var title: String? = null 38 | 39 | private fun init(attrs: AttributeSet?, defStyle: Int) { 40 | // Load attributes 41 | val a = context.obtainStyledAttributes( 42 | attrs, R.styleable.NaviItem, defStyle, 0 43 | ) 44 | title = a.getString( 45 | R.styleable.NaviItem_title 46 | ) 47 | icon = a.getDrawable(R.styleable.NaviItem_icon) 48 | a.recycle() 49 | inflate(context, R.layout.navi_item, this) 50 | 51 | findViewById(R.id.title).text = title 52 | findViewById(R.id.icon).setImageDrawable(icon) 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/view/ViewPagerHelper.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.view 2 | 3 | import android.view.MotionEvent 4 | import androidx.viewpager2.widget.ViewPager2 5 | import kotlin.math.abs 6 | 7 | class ViewPagerHelper(val viewPager2: ViewPager2) { 8 | 9 | private var initialXValue = 0f 10 | private var initialYValue = 0f 11 | fun dispatchTouchEvent(ev: MotionEvent) { 12 | if (ev.action == MotionEvent.ACTION_DOWN) { 13 | initialXValue = ev.x 14 | initialYValue = ev.y 15 | } 16 | if (ev.action == MotionEvent.ACTION_MOVE) { 17 | val diffX: Float = ev.x - initialXValue 18 | val diffY: Float = ev.y - initialYValue 19 | if (abs(diffY) > 1.4 * abs(diffX)) { 20 | if (viewPager2.scrollState != ViewPager2.SCROLL_STATE_DRAGGING) { 21 | viewPager2.isUserInputEnabled = false 22 | } 23 | } 24 | } 25 | if (ev.action == MotionEvent.ACTION_UP) { 26 | initialXValue = 0f 27 | initialYValue = 0f 28 | viewPager2.isUserInputEnabled = true 29 | } 30 | } 31 | 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/im/fdx/v2ex/view/ZoomOutPageTransform.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.view 2 | 3 | import android.annotation.SuppressLint 4 | import android.util.Log 5 | import android.view.View 6 | 7 | import androidx.viewpager.widget.ViewPager 8 | import androidx.viewpager2.widget.ViewPager2 9 | import kotlin.math.abs 10 | import kotlin.math.max 11 | 12 | class ZoomOutPageTransform : ViewPager2.PageTransformer { 13 | 14 | @SuppressLint("NewApi") 15 | override fun transformPage(view: View, position: Float) { 16 | val pageWidth = view.width 17 | val pageHeight = view.height 18 | 19 | Log.e("TAG", "$view , $position") 20 | 21 | when { 22 | position < -1 -> // [-Infinity,-1) 23 | // This page is way off-screen to the left. 24 | view.alpha = 0f 25 | position <= 1 26 | //a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0 27 | -> { // [-1,1] 28 | // Modify the default slide transition to shrink the page as well 29 | val scaleFactor = max(MIN_SCALE, 1 - abs(position)) 30 | val vertMargin = pageHeight * (1 - scaleFactor) / 2 31 | val horzMargin = pageWidth * (1 - scaleFactor) / 2 32 | if (position < 0) { 33 | view.translationX = horzMargin - vertMargin / 2 34 | } else { 35 | view.translationX = -horzMargin + vertMargin / 2 36 | } 37 | 38 | // Scale the page down (between MIN_SCALE and 1) 39 | view.scaleX = scaleFactor 40 | view.scaleY = scaleFactor 41 | 42 | // Fade the page relative to its size. 43 | view.alpha = MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA) 44 | 45 | } 46 | else -> // (1,+Infinity] 47 | // This page is way off-screen to the right. 48 | view.alpha = 0f 49 | } 50 | } 51 | 52 | companion object { 53 | private const val MIN_SCALE = 0.95f 54 | private const val MIN_ALPHA = 1f 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/logo-alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/logo-alpha.png -------------------------------------------------------------------------------- /app/src/main/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/logo.png -------------------------------------------------------------------------------- /app/src/main/res/anim/hide_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/node_title_ctl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/anim/rotate_refresh.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/show_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_night_mode.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/bg_nav_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/drawable-xxhdpi/bg_nav_header.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_shortcut_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/drawable-xxhdpi/ic_shortcut_create.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/loading_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/drawable-xxhdpi/loading_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/loading_image_4_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/drawable-xxhdpi/loading_image_4_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_arrow_drop_down_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_manage_search_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_page_num.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_all_node.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_announcement_hint_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_primary_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_circle_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_downward_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_remove_circle_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_report_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bitcoin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_block_primary_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_block_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_comment.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_daily_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dehaze_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_blue_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_border_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_border_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_message_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_night_mode.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification_with_red_point.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_none_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_primary_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_person_outline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profile.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_primary_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_white_48px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_hint_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_primary_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_thank.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_thank_new.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_thumbs_up.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_twitter.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_website.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_zoom_out_map_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/drawable/logo2x.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/radius_2dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radius_4dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radius_8dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radius_outline_4dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/radius_outline_4dp_primary.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/themed_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_all_nodes.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 19 | 20 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_details.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_follow_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_content.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | 18 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_photo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_web_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/layout/appbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_list_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_et.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_tab_article.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | 19 | 26 | 27 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_all_nodes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 21 | 22 | 34 | 35 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_comments.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 33 | 34 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_load_more.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_node.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_node_simple.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_node_with_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_reply_member.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 27 | 28 | 40 | 41 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_text_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 18 | 19 | 33 | 34 | 35 | 36 | 37 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_topic_with_comments.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 20 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/iv_refresh.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/navi_item.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 20 | 21 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/page_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pager_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 25 | 26 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popwindow_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/simple_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/simple_list_item_center.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/text_view_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_page_number.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_activity_nav_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 42 | 46 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_all_node.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_details.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 16 | 17 | 22 | 23 | 28 | 34 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 10 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_member.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_node.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_reply.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 | 21 | 22 | 23 | 25 | 26 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_tab_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_topic_fragement.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-hdpi/ic_notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-mdpi/ic_notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xhdpi/ic_notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxhdpi/ic_notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_notification_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/app/src/main/res/mipmap-xxxhdpi/ic_notification_icon.png -------------------------------------------------------------------------------- /app/src/main/res/transition/change_image_transform.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/transition/explode.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #cccccc 7 | @color/white 8 | @color/white 9 | @color/white 10 | #cbcbcb 11 | #cbcbcb 12 | 13 | @color/transparent 14 | @color/white 15 | 16 | 17 | #0097A7 18 | @color/base_night 19 | @color/white 20 | @color/white 21 | 22 | 23 | 24 | 25 | #606060 26 | #A3A3A3 27 | #61000000 28 | 29 | 30 | @color/white 31 | @color/white 32 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs_navi_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs_page_number_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 1dp 3 | 1dp 4 | 48dp 5 | 16dp 6 | 7 | 16dp 8 | 14sp 9 | 16dp 10 | 16dp 11 | 160dp 12 | 6dp 13 | 8dp 14 | 16dp 15 | 16 | 17 | 16sp 18 | 14sp 19 | 12sp 20 | 21 | 18sp 22 | 16sp 23 | 14sp 24 | 25 | 20sp 26 | 18sp 27 | 16sp 28 | 29 | 22sp 30 | 20sp 31 | 18sp 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml-v25/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_descriptor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preference.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 18 | 19 | 25 | 26 | 29 | 30 | 38 | 39 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 58 | 59 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preference_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 16 | 17 | 23 | 24 | 25 | 26 | 30 | 36 | 42 | 49 | 50 | 51 | 55 | 56 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/remote_config_defaults.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | google_sign_in_url 5 | https://www.v2ex.com/signin 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/searchable.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/network/HttpHelperTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.network 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Created by fdx on 2016/11/20. 7 | * fdx will maintain it 8 | */ 9 | class HttpHelperTest { 10 | @Test 11 | @Throws(Exception::class) 12 | fun doSomething() { 13 | 14 | // new HttpHelper().doSomething(); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/network/JsonManagerTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.network 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Created by fdx on 2016/11/19. 7 | * fdx will maintain it 8 | */ 9 | class JsonManagerTest { 10 | @Test 11 | @Throws(Exception::class) 12 | fun getOnceCode() { 13 | 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/utils/ContentUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils 2 | 3 | import im.fdx.v2ex.utils.extensions.fullUrl 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | 7 | /** 8 | * Created by fdx on 2016/9/11. 9 | * fdx will maintain it 10 | */ 11 | class ContentUtilsTest { 12 | @Test 13 | @Throws(Exception::class) 14 | fun formatContent() { 15 | val s = "href=\"/go/" 16 | val out = s.fullUrl() 17 | 18 | 19 | assertEquals(out, "href=\"https://www.v2ex.com/go/") 20 | } 21 | 22 | 23 | @Test 24 | @Throws(Exception::class) 25 | fun TimeFormat() { 26 | val s = TimeUtil.getAbsoluteTime("1341262360") 27 | assertEquals("succeed", "2012/07/03", s) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/utils/SecureUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import org.junit.Test 6 | 7 | /** 8 | * Created by fdx on 2017/3/17. 9 | */ 10 | class SecureUtilsTest { 11 | 12 | // public SecureUtils utils = new SecureUtils(); 13 | 14 | //需要mock才能进行,失败 15 | @RequiresApi(api = Build.VERSION_CODES.M) 16 | @Test 17 | @Throws(Exception::class) 18 | fun encrypt() { 19 | // SecureUtils utils = new SecureUtils(); 20 | // String encrypt = utils.encrypt("what the fuck"); 21 | // String decrypt = utils.decrypt(encrypt); 22 | // assertEquals("what the fuck", decrypt); 23 | } 24 | 25 | @Test 26 | @Throws(Exception::class) 27 | fun decrypt() { 28 | 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/utils/StringTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | /** 7 | * Created by fdx on 2017/10/9. 8 | * 9 | */ 10 | class StringTest { 11 | @Test 12 | fun test() { 13 | Assert.assertEquals("android".contains("abcd"), false) 14 | Assert.assertEquals("android".contains("anc"), false) 15 | Assert.assertEquals("android".contains("and"), true) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/utils/TimeUtilTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils 2 | 3 | import org.junit.Test 4 | import java.text.SimpleDateFormat 5 | import java.util.* 6 | 7 | @Suppress("UNUSED_VARIABLE") 8 | /** 9 | * Created by fdx on 2017/7/16. 10 | * fdx will maintain it 11 | */ 12 | class TimeUtilTest { 13 | @Test 14 | fun toLong() { 15 | 16 | val ccc = SimpleDateFormat("yyyy-MM-dd hh:mm:ss a", Locale.US) 17 | val date = ccc.parse("2014-08-08 08:56:06 AM") 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/testDebug/java/im/fdx/v2ex/utils/extensions/StringExtKtTest.kt: -------------------------------------------------------------------------------- 1 | package im.fdx.v2ex.utils.extensions 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Created by fdx on 2017/7/15. 8 | * fdx will maintain it 9 | */ 10 | class StringExtKtTest { 11 | @Test 12 | fun getPair() { 13 | val row = " @fdx #23".findRownum("fdx") 14 | 15 | val row2 = "@fdx sjfksd".findRownum("fdx") 16 | val row3 = "abcdefsdf".findRownum("fdx") 17 | 18 | val row4 = "@fdx sdssds, @ccc #123 , bac \n @3224 #345".findRownum("3224") 19 | assertEquals(row, 23) 20 | assertEquals(row2, -1) 21 | assertEquals(row3, -1) 22 | assertEquals(row4, 345) 23 | 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | 4 | buildscript { 5 | extra["kotlinVersion"] = "1.9.10" 6 | dependencies { 7 | classpath("com.android.tools.build:gradle:8.1.2") 8 | classpath("com.google.gms:google-services:4.4.0") 9 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.extra["kotlinVersion"]}") 10 | classpath("com.google.firebase:firebase-crashlytics-gradle:2.9.9") 11 | classpath ("com.google.firebase:perf-plugin:1.4.2") 12 | } 13 | } 14 | plugins { 15 | id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false 16 | } -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file("C:\\f_code\\key\\pc-api.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one 2 | package_name("im.fdx.v2ex") # e.g. com.krausefx.app 3 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:android) 17 | 18 | platform :android do 19 | desc "Runs all the tests" 20 | lane :test do 21 | gradle(task: "test") 22 | end 23 | 24 | desc "Submit a new Beta Build to Crashlytics Beta" 25 | lane :beta do 26 | gradle(task: "clean assembleRelease") 27 | crashlytics 28 | 29 | # sh "your_script.sh" 30 | # You can also use other beta testing services here 31 | end 32 | 33 | desc "(bundle)Deploy a internal version to the Google Play" 34 | lane :internal do 35 | gradle(task: "clean bundleRelease") 36 | upload_to_play_store(track: 'internal', 37 | skip_upload_metadata: true, 38 | skip_upload_screenshots: true, 39 | skip_upload_images: true, 40 | ) 41 | end 42 | 43 | desc "(bundle)Deploy a new version to production the Google Play" 44 | lane :prod do 45 | gradle(task: "clean bundleRelease") 46 | upload_to_play_store(track: 'production', 47 | skip_upload_metadata: true, 48 | skip_upload_screenshots: true, 49 | skip_upload_images: true, 50 | skip_upload_apk: true, 51 | ) 52 | end 53 | 54 | desc "promote to production" 55 | lane :promote do 56 | upload_to_play_store(track: 'internal', 57 | track_promote_to: 'production', 58 | track_promote_release_status: 'completed', 59 | version_code: , 60 | skip_upload_metadata: true, 61 | skip_upload_screenshots: true, 62 | skip_upload_images: true, 63 | skip_upload_changelogs: true 64 | ) 65 | end 66 | 67 | desc "Build debug and test APK for screenshots" 68 | lane :build_for_screengrab do 69 | gradle( 70 | task: 'clean' 71 | ) 72 | build_android_app( 73 | task: 'assemble', 74 | build_type: 'Debug' 75 | ) 76 | build_android_app( 77 | task: 'assemble', 78 | build_type: 'AndroidTest' 79 | ) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ## Android 17 | 18 | ### android test 19 | 20 | ```sh 21 | [bundle exec] fastlane android test 22 | ``` 23 | 24 | Runs all the tests 25 | 26 | ### android beta 27 | 28 | ```sh 29 | [bundle exec] fastlane android beta 30 | ``` 31 | 32 | Submit a new Beta Build to Crashlytics Beta 33 | 34 | ### android internal 35 | 36 | ```sh 37 | [bundle exec] fastlane android internal 38 | ``` 39 | 40 | (bundle)Deploy a internal version to the Google Play 41 | 42 | ### android prod 43 | 44 | ```sh 45 | [bundle exec] fastlane android prod 46 | ``` 47 | 48 | (bundle)Deploy a new version to production the Google Play 49 | 50 | ### android promote 51 | 52 | ```sh 53 | [bundle exec] fastlane android promote 54 | ``` 55 | 56 | promote to production 57 | 58 | ### android build_for_screengrab 59 | 60 | ```sh 61 | [bundle exec] fastlane android build_for_screengrab 62 | ``` 63 | 64 | Build debug and test APK for screenshots 65 | 66 | ---- 67 | 68 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 69 | 70 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 71 | 72 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 73 | -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/changelogs/61.txt: -------------------------------------------------------------------------------- 1 | * 修复“收藏关注”里网络错误问题 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/changelogs/62.txt: -------------------------------------------------------------------------------- 1 | * 优化用户页面的显示,增加回复总数和主题总数 2 | * 加入全局设置,是否"用户页面"和"我的收藏页面"显示页码 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/changelogs/66.txt: -------------------------------------------------------------------------------- 1 | * 修复可能存在的Google 登录失败的问题 2 | * 增加一些搜索的选项 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/changelogs/69.txt: -------------------------------------------------------------------------------- 1 | * 修复水深火热节点异常 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/changelogs/default.txt: -------------------------------------------------------------------------------- 1 | * 问题修复和性能优化 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 | 主要功能有: 2 | * 使用V2EX的大部分功能,包括浏览主题、发表文章、评论,点赞等 3 | * 个性化设置,包括字体大小,深色主题等 4 | 5 | Github开源地址 https://github.com/fan123199/v2ex-simple -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/featureGraphic.png -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/phoneScreenshots/1_zh-CN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1_zh-CN.jpeg -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/phoneScreenshots/2_zh-CN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2_zh-CN.jpeg -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/phoneScreenshots/3_zh-CN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3_zh-CN.jpeg -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/phoneScreenshots/4_zh-CN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/phoneScreenshots/4_zh-CN.jpeg -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/images/phoneScreenshots/5_zh-CN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/images/phoneScreenshots/5_zh-CN.jpeg -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | V2EX第三方客户端,简洁且功能齐全 -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/title.txt: -------------------------------------------------------------------------------- 1 | 探索路V2EX -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/video.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/fastlane/metadata/android/zh-CN/video.txt -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Thu Aug 10 09:10:35 CST 2017 16 | #systemProp.http.proxyHost=127.0.0.1 17 | #systemProp.http.proxyPort=2080 18 | #systemProp.https.nonProxyHosts=dl.google.com 19 | #systemProp.https.proxyHost=127.0.0.1 20 | #systemProp.https.proxyPort=2080 21 | #systemProp.http.nonProxyHosts=dl.google.com 22 | android.defaults.buildfeatures.buildconfig=true 23 | android.enableJetifier=true 24 | android.nonFinalResIds=false 25 | android.nonTransitiveRClass=false 26 | android.useAndroidX=true 27 | org.gradle.jvmargs=-Xmx2048m 28 | #android.debug.obsoleteApi=true 29 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 21 21:45:50 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-8.0-all.zip 7 | -------------------------------------------------------------------------------- /privacy.md: -------------------------------------------------------------------------------- 1 | # Privacy 2 | 3 | ## Why App have Camera permission? 4 | 5 | A: we use it to take photo to post a topic. It is easy to add photo to a topic. -------------------------------------------------------------------------------- /searchablespinnerlibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /searchablespinnerlibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion '27.0.3' 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 27 10 | versionCode 8 11 | versionName "1.3.1" 12 | } 13 | buildTypes { 14 | } 15 | } 16 | dependencies { 17 | } -------------------------------------------------------------------------------- /searchablespinnerlibrary/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Meditab/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /searchablespinnerlibrary/src/androidTest/java/com/toptoche/searchablespinnerlibrary/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.toptoche.searchablespinnerlibrary; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /searchablespinnerlibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /searchablespinnerlibrary/src/main/res/layout/searchable_list_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /searchablespinnerlibrary/src/main/res/nobleltevzwLMY47XMeditab02192016201518.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fan123199/v2ex-simple/c0778b6aae99b19567671fd5fc858ea007f26820/searchablespinnerlibrary/src/main/res/nobleltevzwLMY47XMeditab02192016201518.gif -------------------------------------------------------------------------------- /searchablespinnerlibrary/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /searchablespinnerlibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SearchableSpinnerLibrary 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url = uri("https://jitpack.io") } 14 | } 15 | } 16 | rootProject.name = "v2ex-simple" 17 | 18 | 19 | include(":app") --------------------------------------------------------------------------------