Full Code of AndyJennifer/SimpleEyes for AI

master 6334fcbc2496 cached
313 files
655.3 KB
170.5k tokens
152 symbols
1 requests
Download .txt
Showing preview only (769K chars total). Download the full file or copy to clipboard to get everything.
Repository: AndyJennifer/SimpleEyes
Branch: master
Commit: 6334fcbc2496
Files: 313
Total size: 655.3 KB

Directory structure:
gitextract_epk5pk4i/

├── .circleci/
│   └── config.yml
├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── dictionaries/
│   │   └── andy.xml
│   ├── encodings.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── jennifer/
│       │               └── andy/
│       │                   └── simpleeyes/
│       │                       └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── assets/
│       │   │   └── fonts/
│       │   │       └── Lobster-1.4.otf
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── jennifer/
│       │   │           └── andy/
│       │   │               └── simpleeyes/
│       │   │                   ├── AndyApplication.kt
│       │   │                   ├── UserPreferences.kt
│       │   │                   ├── entity/
│       │   │                   │   └── AndyInfo.kt
│       │   │                   ├── manager/
│       │   │                   │   └── ActivityManager.kt
│       │   │                   ├── net/
│       │   │                   │   ├── Api.kt
│       │   │                   │   ├── ApiService.kt
│       │   │                   │   ├── Extras.kt
│       │   │                   │   └── RetrofitConfig.kt
│       │   │                   ├── player/
│       │   │                   │   ├── FileMediaDataSource.kt
│       │   │                   │   ├── IjkMediaController.kt
│       │   │                   │   ├── IjkVideoView.kt
│       │   │                   │   ├── IjkVideoViewWrapper.kt
│       │   │                   │   ├── MeasureHelper.kt
│       │   │                   │   ├── PolicyCompat.kt
│       │   │                   │   ├── event/
│       │   │                   │   │   └── VideoProgressEvent.kt
│       │   │                   │   ├── render/
│       │   │                   │   │   ├── IRenderView.kt
│       │   │                   │   │   ├── SurfaceRenderView.kt
│       │   │                   │   │   └── TextureRenderView.kt
│       │   │                   │   └── view/
│       │   │                   │       ├── ControllerView.kt
│       │   │                   │       ├── ControllerViewFactory.kt
│       │   │                   │       ├── ErrorView.kt
│       │   │                   │       ├── FullScreenControllerView.kt
│       │   │                   │       └── TinyControllerView.kt
│       │   │                   ├── router/
│       │   │                   │   ├── EyesPathReplaceService.kt
│       │   │                   │   ├── RouteIntercept.kt
│       │   │                   │   └── SchemeFilterActivity.kt
│       │   │                   ├── rx/
│       │   │                   │   ├── RxBus.kt
│       │   │                   │   ├── RxThreadHelper.kt
│       │   │                   │   └── error/
│       │   │                   │       ├── FlowableRetryDelay.kt
│       │   │                   │       ├── GlobalErrorProcessor.kt
│       │   │                   │       ├── GlobalErrorTransformer.kt
│       │   │                   │       ├── ObservabeRetryDelay.kt
│       │   │                   │       └── RetryConfig.kt
│       │   │                   ├── ui/
│       │   │                   │   ├── MainActivity.kt
│       │   │                   │   ├── author/
│       │   │                   │   │   ├── AuthorTagDetailActivity.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── AuthorModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   └── AuthorTagDetailPresenter.kt
│       │   │                   │   │   └── ui/
│       │   │                   │   │       └── AuthorTagDetailView.kt
│       │   │                   │   ├── base/
│       │   │                   │   │   ├── BaseActivity.kt
│       │   │                   │   │   ├── BaseAppCompatActivity.kt
│       │   │                   │   │   ├── BaseAppCompatFragment.kt
│       │   │                   │   │   ├── BaseFragment.kt
│       │   │                   │   │   ├── BaseFragmentItemAdapter.kt
│       │   │                   │   │   ├── BaseView.kt
│       │   │                   │   │   ├── LoadMoreView.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   └── BaseDataAdapter.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── BaseModel.kt
│       │   │                   │   │   └── presenter/
│       │   │                   │   │       ├── BasePresenter.kt
│       │   │                   │   │       └── LoadMorePresenter.kt
│       │   │                   │   ├── common/
│       │   │                   │   │   ├── CommonModel.kt
│       │   │                   │   │   ├── CommonPresenter.kt
│       │   │                   │   │   ├── CommonRecyclerActivity.kt
│       │   │                   │   │   └── CommonView.kt
│       │   │                   │   ├── feed/
│       │   │                   │   │   ├── AllCategoryActivity.kt
│       │   │                   │   │   ├── CategoryTabActivity.kt
│       │   │                   │   │   ├── FeedFragment.kt
│       │   │                   │   │   ├── RankListActivity.kt
│       │   │                   │   │   ├── TagActivity.kt
│       │   │                   │   │   ├── TagDetailInfoFragment.kt
│       │   │                   │   │   ├── TopicActivity.kt
│       │   │                   │   │   ├── WebViewActivity.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── FeedModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── AllCategoryPresenter.kt
│       │   │                   │   │   │   ├── CategoryTabPresenter.kt
│       │   │                   │   │   │   ├── FeedPresenter.kt
│       │   │                   │   │   │   ├── RankListPresenter.kt
│       │   │                   │   │   │   ├── TagDetailInfoPresenter.kt
│       │   │                   │   │   │   └── TopicPresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── AllCategoryView.kt
│       │   │                   │   │       ├── CategoryTabView.kt
│       │   │                   │   │       ├── FeedView.kt
│       │   │                   │   │       ├── RankListView.kt
│       │   │                   │   │       ├── TagDetailInfoView.kt
│       │   │                   │   │       └── TopicView.kt
│       │   │                   │   ├── follow/
│       │   │                   │   │   ├── AllAuthorActivity.kt
│       │   │                   │   │   ├── FollowFragment.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── FollowModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── AllAuthorPresenter.kt
│       │   │                   │   │   │   └── FollowPresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── AllAuthorView.kt
│       │   │                   │   │       └── FollowView.kt
│       │   │                   │   ├── home/
│       │   │                   │   │   ├── DailyEliteActivity.kt
│       │   │                   │   │   ├── HomeFragment.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   ├── CollectionCardCoverAdapter.kt
│       │   │                   │   │   │   ├── DailyEliteAdapter.kt
│       │   │                   │   │   │   └── SquareCollectionAdapter.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── HomeModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── DailyElitePresenter.kt
│       │   │                   │   │   │   └── HomePresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── DailyEliteView.kt
│       │   │                   │   │       └── HomeView.kt
│       │   │                   │   ├── login/
│       │   │                   │   │   └── LoginActivity.kt
│       │   │                   │   ├── profile/
│       │   │                   │   │   ├── CacheFragment.kt
│       │   │                   │   │   ├── ProfileFragment.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   └── ProfileSettingAdapter.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── ProfileModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── CachePresenter.kt
│       │   │                   │   │   │   └── ProfilePresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── CacheView.kt
│       │   │                   │   │       └── ProfileView.kt
│       │   │                   │   ├── search/
│       │   │                   │   │   ├── SearchHotActivity.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   ├── CollectionBriefAdapter.kt
│       │   │                   │   │   │   ├── SearchHotAdapter.kt
│       │   │                   │   │   │   └── SearchVideoAdapter.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   └── SearchPresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       └── SearchView.kt
│       │   │                   │   ├── splash/
│       │   │                   │   │   ├── LandingActivity.kt
│       │   │                   │   │   ├── LocalCommonLandingFragment.kt
│       │   │                   │   │   ├── SloganFragment.kt
│       │   │                   │   │   ├── VideoLandingFragment.kt
│       │   │                   │   │   └── adapter/
│       │   │                   │   │       └── SplashVideoFragmentAdapter.kt
│       │   │                   │   └── video/
│       │   │                   │       ├── VideoDetailActivity.kt
│       │   │                   │       ├── VideoInfoByIdActivity.kt
│       │   │                   │       ├── adapter/
│       │   │                   │       │   └── VideoDetailAdapter.kt
│       │   │                   │       ├── model/
│       │   │                   │       │   └── VideoDetailModel.kt
│       │   │                   │       ├── presenter/
│       │   │                   │       │   ├── VideoDetailPresenter.kt
│       │   │                   │       │   └── VideoInfoByIdPresenter.kt
│       │   │                   │       └── view/
│       │   │                   │           ├── VideoDetailView.kt
│       │   │                   │           └── VideoInfoByIdView.kt
│       │   │                   ├── utils/
│       │   │                   │   ├── AppUtils.kt
│       │   │                   │   ├── ButterKnife.kt
│       │   │                   │   ├── DensityUtils.kt
│       │   │                   │   ├── IntentRouterUtils.kt
│       │   │                   │   ├── KeyboardUtils.kt
│       │   │                   │   ├── NetWorkUtils.kt
│       │   │                   │   ├── ProcessUtils.kt
│       │   │                   │   ├── ScreenUtils.kt
│       │   │                   │   ├── SystemUtils.kt
│       │   │                   │   ├── TimeUtils.kt
│       │   │                   │   ├── ToastUtils.kt
│       │   │                   │   ├── UDIDUtils.kt
│       │   │                   │   ├── VideoPlayerUtils.kt
│       │   │                   │   └── ext/
│       │   │                   │       └── IntentEx.kt
│       │   │                   └── widget/
│       │   │                       ├── BottomBar.kt
│       │   │                       ├── BottomItem.kt
│       │   │                       ├── BottomItemLayout.kt
│       │   │                       ├── CollectionOfHorizontalScrollCardView.kt
│       │   │                       ├── CustomLoadMoreView.kt
│       │   │                       ├── EliteImageView.kt
│       │   │                       ├── FullScreenVideoView.kt
│       │   │                       ├── GridItemDecoration.kt
│       │   │                       ├── ItemHeaderView.kt
│       │   │                       ├── SearchHotRemindView.kt
│       │   │                       ├── StickyNavLayout.kt
│       │   │                       ├── VerticalProgressBar.kt
│       │   │                       ├── VideoDetailAuthorView.kt
│       │   │                       ├── font/
│       │   │                       │   ├── CustomFontTextView.kt
│       │   │                       │   ├── CustomFontTypeWriterTextView.kt
│       │   │                       │   ├── FontType.kt
│       │   │                       │   ├── PrintSpan.kt
│       │   │                       │   ├── PrintSpanGroup.kt
│       │   │                       │   └── TypefaceManager.kt
│       │   │                       ├── image/
│       │   │                       │   ├── CenterAlignImageSpan.kt
│       │   │                       │   └── imageloader/
│       │   │                       │       └── FrescoImageLoader.kt
│       │   │                       ├── pull/
│       │   │                       │   ├── head/
│       │   │                       │   │   ├── EliteHeaderView.kt
│       │   │                       │   │   ├── HeaderRefreshView.kt
│       │   │                       │   │   ├── HomePageHeaderView.kt
│       │   │                       │   │   └── VideoDetailHeadView.kt
│       │   │                       │   ├── refresh/
│       │   │                       │   │   ├── LinearLayoutManagerWithSmoothScroller.kt
│       │   │                       │   │   ├── PullRefreshView.kt
│       │   │                       │   │   ├── PullToRefresh.kt
│       │   │                       │   │   ├── PullToRefreshBase.kt
│       │   │                       │   │   └── PullToRefreshRecyclerView.kt
│       │   │                       │   └── zoom/
│       │   │                       │       ├── PullToZoom.kt
│       │   │                       │       ├── PullToZoomBase.kt
│       │   │                       │       └── PullToZoomRecyclerView.kt
│       │   │                       ├── state/
│       │   │                       │   ├── MultipleStateView.kt
│       │   │                       │   └── NetLoadingView.kt
│       │   │                       ├── tab/
│       │   │                       │   ├── AnimationUtils.java
│       │   │                       │   ├── ShortTabLayout.java
│       │   │                       │   └── TabItem.java
│       │   │                       └── viewpager/
│       │   │                           ├── InterceptVerticalViewPager.kt
│       │   │                           └── MarginWithIndicatorViewPager.kt
│       │   └── res/
│       │       ├── anim/
│       │       │   ├── bottom_in.xml
│       │       │   ├── bottom_out.xml
│       │       │   ├── fade_in.xml
│       │       │   ├── fade_out.xml
│       │       │   ├── left_in.xml
│       │       │   ├── left_out.xml
│       │       │   ├── no_anim.xml
│       │       │   ├── right_in.xml
│       │       │   ├── right_out.xml
│       │       │   ├── scale_in.xml
│       │       │   ├── scale_out.xml
│       │       │   ├── top_in.xml
│       │       │   └── top_out.xml
│       │       ├── color/
│       │       │   └── selector_item_square_text.xml
│       │       ├── drawable/
│       │       │   ├── ic_launcher_background.xml
│       │       │   ├── seek_bar_layer.xml
│       │       │   ├── selector_checkbox_bg.xml
│       │       │   ├── selector_item_square_foreground.xml
│       │       │   ├── shape_black_border.xml
│       │       │   ├── shape_border_bottom.xml
│       │       │   ├── shape_border_bottom_top.xml
│       │       │   ├── shape_hot_search_bg.xml
│       │       │   ├── shape_indicator_selected.xml
│       │       │   ├── shape_indicator_unselected.xml
│       │       │   ├── shape_share_bg.xml
│       │       │   ├── shape_show_all_border.xml
│       │       │   ├── shape_translate_border.xml
│       │       │   ├── shape_video_detail_placeholder.xml
│       │       │   ├── shape_webview_scrollbar.xml
│       │       │   └── shape_white_border.xml
│       │       ├── layout/
│       │       │   ├── activity_all_author.xml
│       │       │   ├── activity_all_category.xml
│       │       │   ├── activity_author_tag_detail.xml
│       │       │   ├── activity_category_tab.xml
│       │       │   ├── activity_common_recyclerview.xml
│       │       │   ├── activity_daily_elite.xml
│       │       │   ├── activity_landing.xml
│       │       │   ├── activity_login.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── activity_rank.xml
│       │       │   ├── activity_tag.xml
│       │       │   ├── activity_topic.xml
│       │       │   ├── activity_video_detail.xml
│       │       │   ├── activity_videoinfo_by_id.xml
│       │       │   ├── activity_webview.xml
│       │       │   ├── dialog_light_controller.xml
│       │       │   ├── dialog_volume_controller.xml
│       │       │   ├── empty_search_word.xml
│       │       │   ├── fragment_cache.xml
│       │       │   ├── fragment_feed.xml
│       │       │   ├── fragment_follow.xml
│       │       │   ├── fragment_home.xml
│       │       │   ├── fragment_local_coomon_landing.xml
│       │       │   ├── fragment_profile.xml
│       │       │   ├── fragment_search_hot.xml
│       │       │   ├── fragment_slogan.xml
│       │       │   ├── fragment_tag_detail_info.xml
│       │       │   ├── fragment_video_landing.xml
│       │       │   ├── item_collection_brief.xml
│       │       │   ├── item_collection_card_cover.xml
│       │       │   ├── item_collection_of_horizontal_scroll_card.xml
│       │       │   ├── item_hot_search.xml
│       │       │   ├── item_profile_setting.xml
│       │       │   ├── item_square_card.xml
│       │       │   ├── item_square_collection.xml
│       │       │   ├── item_the_end.xml
│       │       │   ├── item_video_samll_card.xml
│       │       │   ├── item_video_text_card.xml
│       │       │   ├── layout_author_tag_detail_header.xml
│       │       │   ├── layout_blank_card.xml
│       │       │   ├── layout_bottom_item.xml
│       │       │   ├── layout_brife_card.xml
│       │       │   ├── layout_card_banner.xml
│       │       │   ├── layout_category_head_view.xml
│       │       │   ├── layout_category_tab_toolbar.xml
│       │       │   ├── layout_center_title_share_toolbar.xml
│       │       │   ├── layout_choiceness.xml
│       │       │   ├── layout_collection_of_horizontal_scroll_card.xml
│       │       │   ├── layout_collection_with_brief.xml
│       │       │   ├── layout_collection_with_cover.xml
│       │       │   ├── layout_common_text.xml
│       │       │   ├── layout_common_toolbar.xml
│       │       │   ├── layout_division_line.xml
│       │       │   ├── layout_follow_card.xml
│       │       │   ├── layout_horizontal_scroll_card.xml
│       │       │   ├── layout_ijk_wrapper.xml
│       │       │   ├── layout_left_title_share_toolbar.xml
│       │       │   ├── layout_load_more_view.xml
│       │       │   ├── layout_loading_message.xml
│       │       │   ├── layout_loading_view.xml
│       │       │   ├── layout_margin_with_indicator_pager.xml
│       │       │   ├── layout_media_controller_full_screen.xml
│       │       │   ├── layout_media_controller_tiny.xml
│       │       │   ├── layout_search_hot_remind_view.xml
│       │       │   ├── layout_single_text.xml
│       │       │   ├── layout_single_video.xml
│       │       │   ├── layout_square_collection.xml
│       │       │   ├── layout_video_author_head.xml
│       │       │   ├── layout_video_detail_head.xml
│       │       │   ├── layout_video_error.xml
│       │       │   ├── layout_video_small_card.xml
│       │       │   ├── refresh_category_header.xml
│       │       │   └── refresh_daily_elite_header.xml
│       │       ├── layout-v21/
│       │       │   └── layout_square_collection.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── values/
│       │       │   ├── attrs.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── ids_sticky_nav_layout.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       └── xml/
│       │           └── network_security_config.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── jennifer/
│                       └── andy/
│                           └── simpleeyes/
│                               └── ExampleUnitTest.kt
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
references:
  cache_key: &cache_key
    key: jars-{{ checksum "build.gradle" }}-{{ checksum  "app/build.gradle" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}

jobs:
  build:
    docker:
      - image: circleci/android:api-28-alpha
    environment:
      JAVA_TOOL_OPTIONS: "-Xmx1024m"
      GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2"
      TERM: dumb
    steps:
      - checkout
      - restore_cache:
          <<: *cache_key
      - run:
          name: Download Dependencies
          command: ./gradlew dependencies
      - save_cache:
          <<: *cache_key
          paths:
            - ~/.gradle/caches
            - ~/.gradle/wrapper
      - run:
          name: Run JVM Tests & Lint
          command: ./gradlew check
      - store_artifacts:
          path: app/build/reports
          destination: reports
      - store_test_results:
          path: app/build/test-results

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild


================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <codeStyleSettings language="XML">
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
      <arrangement>
        <rules>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:android</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:id</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>style</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>ANDROID_ATTRIBUTE_ORDER</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
        </rules>
      </arrangement>
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
  </state>
</component>

================================================
FILE: .idea/dictionaries/andy.xml
================================================
<component name="ProjectDictionaryState">
  <dictionary name="andy" />
</component>

================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
    <file url="PROJECT" charset="UTF-8" />
  </component>
</project>

================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="distributionType" value="DEFAULT_WRAPPED" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="gradleHome" value="$USER_HOME$/Downloads/gradle-4.6" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
          </set>
        </option>
        <option name="resolveModulePerSourceSet" value="false" />
      </GradleProjectSettings>
    </option>
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="NullableNotNullManager">
    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
    <option name="myNullables">
      <value>
        <list size="12">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
          <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
          <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
          <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
          <item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
          <item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
          <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
          <item index="10" class="java.lang.String" itemvalue="android.annotation.Nullable" />
          <item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
        </list>
      </value>
    </option>
    <option name="myNotNulls">
      <value>
        <list size="11">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
          <item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
          <item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
          <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
          <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
          <item index="9" class="java.lang.String" itemvalue="android.annotation.NonNull" />
          <item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
        </list>
      </value>
    </option>
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/SimpleEyes.iml" filepath="$PROJECT_DIR$/.idea/SimpleEyes.iml" />
      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>

================================================
FILE: LICENSE
================================================
   Copyright [2019] [AndyJennifer]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# SimpleEyes

[![CircleCI](https://circleci.com/gh/AndyJennifer/SimpleEyes.svg?style=shield)](https://circleci.com/gh/AndyJennifer/SimpleEyes)
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)

SimpleEyes 一款基于 Kotlin 开发的短视频项目。该项目为如下两个分支:

- [master 分支](https://github.com/AndyJennifer/SimpleEyes/tree/master) :MVP + Retrofit + Rxjava
- [jetpack 分支](https://github.com/AndyJennifer/SimpleEyes/tree/simpleeyes-jetpack):MVVM + Android Jetpack + Retrofit + RxJava 

你可以根据自己的需要,选择不同的分支。

## 项目展示 ☘

![picture_1.png](https://upload-images.jianshu.io/upload_images/2824145-9c4c8943bc9eebc7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

## 闪光点 ✨✨

- 100% 纯 Kotlin 开发。巧妙了运用了 Kotlin 的诸多特性,如扩展函数,数据类,委托等。
- 丰富的动画与自定义View。如下拉刷新、视差动画、TextView打字动画、嵌套滑动,状态布局。
- RxJava 常用操作符的使用,并新增了全局错误处理。
- 支持视频的横竖屏切换、音量控制、亮度控制等。
- 良好的代码规范与注释。
- 良好的设计模式与架构。
- .....

## 完成的功能  💼

- 闪屏页实现
  - [x] 开场短视频
  - [x] 开场动画效果1、2
- 首页
  - [x] 底部 Tab 切换
  - [x] 顶部轮播
  - [x] 下拉视差刷新,加载更多
  - [x] 视频搜索
  - [x] 每日精选
  - [x] 登录页
- 发现
  - [x] 热门、分类、作者、
  - [x] 全部分类
  - [x] 视频搜索
- 关注
  - [x] 全部作者
  - [x] 作者简介及视频
- 我的
  - [ ] 我的消息
  - [ ] 我的缓存

## 使用的库 💪

站在巨人的肩膀上。可以看得更远。该项目中运用了以下开源库:

- [Fresco](https://github.com/facebook/fresco)
- [ARouter](https://github.com/alibaba/ARouter)
- [Fragmentation](https://github.com/YoKeyword/Fragmentation)
- [RxJava](https://github.com/ReactiveX/RxJava)
- [Retrofit](https://github.com/square/retrofit)
- [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)
- [IjkPlayer](https://github.com/Bilibili/ijkplayer)
- [FlexBox-Layout](https://github.com/google/flexbox-layout)
- [Banner](https://github.com/youth5201314/banner)
- [Android KTX](https://developer.android.google.cn/kotlin/ktx)
- [Koin](https://github.com/InsertKoinIO/koin)

## 声明 📢

感谢 [开眼App](http://www.kaiyanapp.com) 提供参考,本人是豆瓣粉丝,使用了其中的 Api ,并非攻击。如构成侵权,请及时通知我删除或者修改。数据来源来自[开眼](https://www.kaiyanapp.com/) ,一切解释权归开眼所有。

## 最后

注意:此开源项目仅做学习交流使用。如用到实际项目,还需多考虑其他因素,请多多斟酌。如果你觉得该项目不错,欢迎点击 star ❤️,follow,也可以帮忙分享给你更多的朋友。你的支持与鼓励是给我继续做好该项目的最大动力。

## 联系我

- QQ:443696320
- 简书:[AndyandJennifer](https://www.jianshu.com/users/921c778fb5e1/timeline)
- 掘金:[AndyandJennifer](https://juejin.im/user/5acc1ea06fb9a028bc2e0fc1)
- Email: [andyjennifer@126.com](andyjennifer@126.com)

## License

```text
   Copyright [2019] [AndyJennifer]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
```


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}
android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'
    defaultConfig {
        applicationId "com.jennifer.andy.simpleeyes"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
    
    kotlinOptions{
        jvmTarget = "1.8"
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'com.google.android.material:material:1.0.0'

    //RxJava
    implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'

    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

    //fragmentation
    implementation 'me.yokeyword:fragmentationx:1.0.1'

    //recyclerView
    implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.44'

    //fresco
    implementation 'com.facebook.fresco:fresco:1.11.0'

    //ijk
    implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
    implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
    implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8'
    implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
    implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
    implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'

    //loadingView
    implementation 'com.wang.avi:library:2.1.3'
    implementation 'com.youth.banner:banner:1.4.10'

    //flex
    implementation 'com.google.android:flexbox:0.3.2'

    //pageIndicator
    implementation 'com.romandanylyk:pageindicatorview:1.0.1@aar'

    //ARouter
    api 'com.alibaba:arouter-api:1.4.1'
    kapt 'com.alibaba:arouter-compiler:1.2.2'

    //LeakCanary
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
    debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

    //ktx
    implementation 'androidx.core:core-ktx:1.1.0'

    //autodispose
    implementation 'com.uber.autodispose:autodispose-android:1.4.0'
    implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.4.0'
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/andy/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


================================================
FILE: app/src/androidTest/java/com/jennifer/andy/simpleeyes/ExampleInstrumentedTest.kt
================================================
package com.jennifer.andy.simpleeyes

import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getTargetContext()
        assertEquals("com.jennifer.andy.simplemusic", appContext.packageName)
    }
}


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.jennifer.andy.simpleeyes">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <application
        android:name=".AndyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="UnusedAttribute">

        <!--中间跳转页-->
        <activity android:name=".router.SchemeFilterActivity">
            <!--Scheme-->
            <intent-filter>
                <data android:scheme="eyepetizer" />
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>


            <!-- 网页跳转到App-->
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="www.kaiyanapp.com"
                    android:scheme="http" />
                <data
                    android:host="www.kaiyanapp.com/"
                    android:scheme="https" />
            </intent-filter>
        </activity>

        <!--加载界面-->
        <activity
            android:name=".ui.splash.LandingActivity"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme"
            tools:ignore="LockedOrientationActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


        <!--主界面-->
        <activity
            android:name=".ui.MainActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--登录界面-->
        <activity
            android:name=".ui.login.LoginActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--视频播放界面-->
        <activity
            android:name=".ui.video.VideoDetailActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />
        <!--搜索界面-->
        <activity
            android:name=".ui.search.SearchHotActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />
        <!--每日精选-->
        <activity
            android:name=".ui.home.DailyEliteActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />
        <!--全部分类-->
        <activity
            android:name=".ui.feed.AllCategoryActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--网页展示Activity-->
        <activity
            android:name=".ui.feed.WebViewActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--根据视频id获取视频信息-->
        <activity
            android:name=".ui.video.VideoInfoByIdActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />
        <!--排行榜-->
        <activity
            android:name=".ui.feed.RankListActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--热门专题-->
        <activity
            android:name=".ui.feed.TopicActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--360全景-->
        <activity
            android:name=".ui.feed.TagActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--种类下tab信息-->
        <activity
            android:name=".ui.feed.CategoryTabActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--全部作者-->
        <activity
            android:name=".ui.follow.AllAuthorActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--作者信息详细界面-->
        <activity
            android:name=".ui.author.AuthorTagDetailActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />

        <!--公共RecyclerView界面,根据url加载数据-->
        <activity
            android:name=".ui.common.CommonRecyclerActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            tools:ignore="LockedOrientationActivity" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/AndyApplication.kt
================================================
package com.jennifer.andy.simpleeyes

import android.app.Application
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.squareup.leakcanary.LeakCanary


/**
 * Author:  andy.xwt
 * Date:    2017/10/15 09:27
 * Description:
 */

class AndyApplication : Application() {

    companion object {

        lateinit var INSTANCE: Application

    }

    override fun onCreate() {
        super.onCreate()
        INSTANCE = this
        initARoute()
        initFresco()
        initLeakCanary()
    }

    /**
     * 初始化路由操作
     */
    private fun initARoute() {
        if (BuildConfig.DEBUG) {
            ARouter.openLog()
            ARouter.openDebug()
        }
        ARouter.init(this)
    }

    /**
     * 初始化Fresco,打开压缩
     */
    private fun initFresco() {
        val config = ImagePipelineConfig.newBuilder(this)
                .setDownsampleEnabled(true)
                .build()
        Fresco.initialize(this, config)
    }

    /**
     * 初始化LeakCanary,检测内存泄露
     */
    private fun initLeakCanary() {
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return
        }
        LeakCanary.install(this)
    }


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/UserPreferences.kt
================================================
package com.jennifer.andy.simpleeyes

import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit


/**
 * Author:  andy.xwt
 * Date:    2018/4/9 09:34
 * Description:用户配置信息
 */

object UserPreferences {

    private const val NAME = "andy."
    private const val KEY_IS_USER_LOGIN = "is_user_login"
    private const val KEY_IS_FIRST_LOGIN = "is_first_login"
    private const val KEY_IS_SHOW_USER_ANIM = "key_is_show_user_anim"

    /**
     * 保存用户是否登录配置
     */
    fun saveUserIsLogin(isUserLogin: Boolean) {
        getSharedPreferences().edit {
            putBoolean(KEY_IS_USER_LOGIN, isUserLogin)
        }
    }

    /**
     * 是否像用户展示过动画
     */
    fun saveShowUserAnim(isUserLogin: Boolean) {
        getSharedPreferences().edit {
            putBoolean(KEY_IS_SHOW_USER_ANIM, isUserLogin)
        }
    }

    /**
     * 保存用户是否是第一次登录
     */
    fun saveUserIsFirstLogin(isFirstLogin: Boolean) {
        getSharedPreferences().edit {
            putBoolean(KEY_IS_FIRST_LOGIN, isFirstLogin)
        }
    }

    /**
     * 获取用户是否登录
     */
    fun getUserIsLogin() = getSharedPreferences().getBoolean(KEY_IS_USER_LOGIN, false)

    /**
     * 是否像用户展示过动画
     */
    fun getShowUserAnim() = getSharedPreferences().getBoolean(KEY_IS_SHOW_USER_ANIM, false);

    /**
     * 获取用户是否是第一次登录
     */
    fun getUserIsFirstLogin() = getSharedPreferences().getBoolean(KEY_IS_FIRST_LOGIN, true)


    private fun getSharedPreferences(): SharedPreferences {
        return AndyApplication.INSTANCE.getSharedPreferences(NAME, Context.MODE_PRIVATE)
    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/entity/AndyInfo.kt
================================================
package com.jennifer.andy.simpleeyes.entity

import java.io.Serializable

/**
 * Author:  andy.xwt
 * Date:    2017/10/19 19:00
 * Description: 页面数据信息,主要是视频信息
 */


data class AndyInfo(var count: Int,
                    var total: Int,
                    var nextPageUrl: String?,
                    var date: Long,
                    var nextPublishTime: Long,
                    var topIssue: TopIssue,
                    var refreshCount: Int,
                    var lastStartId: Int,
                    var itemList: MutableList<Content>) : Serializable

data class JenniferInfo(var nextPageUrl: String?,
                        var date: Long,
                        var nextPublishTime: Long,
                        var topIssue: TopIssue,
                        var refreshCount: Int,
                        var lastStartId: Int,
                        var issueList: MutableList<ContentBean>) : Serializable

data class Content(var type: String,
                   var data: ContentBean,
                   var id: String,
                   var date: Long,
                   var tag: String) : Serializable


data class ContentBean(var dataType: String,
                       var header: Header?,
                       var follow: FollowInfo?,
                       var content: Content?,
                       var itemList: MutableList<Content>,
                       var id: String,
                       var title: String,
                       var description: String,
                       var library: String,
                       var tags: MutableList<TagsBean>,
                       var consumption: ConsumptionBean,
                       var image: String,
                       var resourceType: String,
                       var slogan: String,
                       var provider: ProviderBean,
                       var category: String,
                       var author: AuthorBean,
                       var cover: CoverBean,
                       var playUrl: String,
                       var thumbPlayUrl: String,
                       var duration: Int,
                       var webUrl: WebUrlBean,
                       var releaseTime: Long,
                       var playInfo: MutableList<PlayInfoBean>,
                       var campaign: String,
                       var waterMarks: String,
                       var type: String?,
                       var titlePgc: String,
                       var descriptionPgc: String,
                       var remark: String,
                       var idx: Int,
                       var shareAdTrack: String,
                       var favoriteAdTrack: String,
                       var webAdTrack: String,
                       var date: Long,
                       val label: Label?,
                       var labelList: MutableList<*>,
                       var descriptionEditor: String,
                       var isCollected: Boolean,
                       var isPlayed: Boolean,
                       var subtitles: MutableList<*>,
                       var lastViewTime: String,
                       var playlists: String,
                       var text: String,
                       var icon: String,
                       var iconType: String,
                       var height: Int,
                       var src: Int,
                       var actionUrl: String?) : Serializable


data class Header(var id: String,
                  var title: String?,
                  var subTitle: String?,
                  var subTitleFont: String,
                  var textAlign: String?,
                  var font: String,
                  var cover: String,
                  var label: Any,
                  var actionUrl: String?,
                  var labelList: MutableList<*>,
                  var icon: String?,
                  var iconType: String,
                  var description: String?,
                  var follow: FollowInfo?,
                  var time: Long?) : Serializable


data class Label(var text: String,
                 var card: String,
                 var detail: String) : Serializable

data class TagsBean(var id: Int,
                    var name: String,
                    var actionUrl: String,
                    var adTrack: String) : Serializable


data class ConsumptionBean(var collectionCount: String,
                           var shareCount: String,
                           var replyCount: String) : Serializable


data class TopIssue(var type: String,
                    var data: TopIssueBean,
                    var tag: String) : Serializable

data class TopIssueBean(var dataType: String,
                        var count: Int,
                        var itemList: MutableList<Content>) : Serializable


data class ProviderBean(var name: String,
                        var alias: String,
                        var icon: String) : Serializable

data class AuthorBean(var id: String,
                      var icon: String,
                      var name: String,
                      var description: String,
                      var link: String,
                      var latestReleaseTime: Long,
                      var videoNum: Int,
                      var adTrack: String,
                      var follow: FollowInfo,
                      var shield: ShieldBean,
                      var approvedNotReadyVideoCount: Int,
                      var isIfPgc: Boolean = false) : Serializable


data class ShieldBean(
        var itemType: String,
        var itemId: Int,
        var isShielded: Boolean) : Serializable


data class CoverBean(var feed: String,
                     var detail: String,
                     var blurred: String,
                     var sharing: String,
                     var homepage: String) : Serializable


data class WebUrlBean(var raw: String,
                      var forWeibo: String) : Serializable


data class PlayInfoBean(var height: Int,
                        var width: Int,
                        var name: String,
                        var type: String,
                        var url: String,
                        var urlList: MutableList<UrlListBean>) : Serializable

data class UrlListBean(var name: String,
                       var url: String,
                       var size: Double) : Serializable


data class Category(var categoryInfo: CategoryInfo,
                    var tabInfo: TabInfo)

data class CategoryInfo(var dataType: String,
                        var id: String,
                        var name: String,
                        var description: String,
                        var headerImage: String,
                        var actionUrl: String,
                        var followInfo: FollowInfo)


data class Tab(var tabInfo: TabInfo,
               var pgcInfo: PgcInfo) : Serializable

data class TabInfo(var tabList: MutableList<TabDetailInfo>,
                   var defaultIdx: Int) : Serializable


data class TabDetailInfo(var id: Int,
                         var name: String,
                         var apiUrl: String) : Serializable


data class FollowInfo(var itemType: String,
                      var itemId: String,
                      var followed: Boolean) : Serializable


data class PgcInfo(var dataType: String,
                   var id: Int,
                   var icon: String,
                   var name: String,
                   var brief: String,
                   var description: String,
                   var actionUrl: String,
                   var followCount: Int,
                   var shareCount: Int,
                   var videoCount: Int,
                   var collectCount: Int,
                   var followInfo: FollowInfo) : Serializable

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/manager/ActivityManager.kt
================================================
package com.jennifer.andy.simpleeyes.manager

import android.app.Activity


/**
 * Author:  andy.xwt
 * Date:    2017/8/14 00:15
 * Description:Activity管理类
 */

class ActivityManager private constructor() {

    private val sActivityList = ArrayList<Activity>()

    companion object {
        fun getInstance(): ActivityManager {
            return BaseAppManagerHolder.instance
        }
    }

    private object BaseAppManagerHolder {
        val instance = ActivityManager()
    }

    /**
     * 当前activity的个数
     */
    val mActivitySize: Int get() = sActivityList.size

    val mForwardActivity: Activity? @Synchronized get() = if (mActivitySize > 0) sActivityList[mActivitySize - 1] else null

    /**
     * 添加相应activity
     */
    @Synchronized
    fun removeActivity(activity: Activity) {
        if (activity in sActivityList) sActivityList.remove(activity)
    }

    /**
     * 添加相应的activity
     */
    @Synchronized
    fun addActivity(activity: Activity) {
        sActivityList.add(activity)
    }

    /**
     * 清除栈内activity
     */
    @Synchronized
    fun clear() {
        var i = sActivityList.size - 1
        while (i > -1) {
            val activity = sActivityList[i]
            removeActivity(activity)
            activity.finish()
            i = sActivityList.size
            i--
        }
    }

    /**
     * 清除栈顶activity
     */
    @Synchronized
    fun clearToTop() {
        var i = sActivityList.size - 2
        while (i > -1) {
            val activity = sActivityList[i]
            removeActivity(activity)
            activity.finish()
            i = sActivityList.size - 1
            i--
        }
    }

    /**
     * 获取最上层的Activity
     */
    @Synchronized
    fun getTopActivity(): Activity? {
        return if (sActivityList.isNotEmpty()) {
            sActivityList[sActivityList.size - 1]
        } else {
            null
        }

    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/Api.kt
================================================
package com.jennifer.andy.simpleeyes.net


/**
 * Author:  andy.xwt
 * Date:    2017/10/15 09:54
 * Description:
 */

object Api {


    /**
     * 主域名
     */
    val BASE_URL: String get() = "http://baobab.kaiyanapp.com/"

    /**
     * 获取默认Service
     */
    fun getDefault() = RetrofitConfig.getDefaultService()


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/ApiService.kt
================================================
package com.jennifer.andy.simpleeyes.net

import com.jennifer.andy.simpleeyes.entity.*
import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*


/**
 * Author:  andy.xwt
 * Date:    2017/10/10 22:46
 * Description:
 */

interface ApiService {

    ///////////////////////////////////////////////////////////////////////////
    // 首页相关
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 首页
     */
    @GET("api/v4/tabs/selected")
    fun getHomeInfo(): Observable<AndyInfo>

    /**
     * 获取热门关键词
     */
    @GET("api/v3/queries/hot")
    fun getHotWord(): Observable<MutableList<String>>

    /**
     * 关键词搜索
     */
    @GET("api/v1/search")
    fun searchVideoByWord(@Query("query") word: String): Observable<AndyInfo>

    /**
     * 每日精选旁的日历显示
     */
    @GET("api/v3/issueNavigationList")
    fun getIssueNaviGationList(): Observable<JenniferInfo>

    ///////////////////////////////////////////////////////////////////////////
    // 发现相关
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 发现
     */
    @GET("api/v4/discovery")
    fun getDiscoveryTab(): Observable<Tab>

    /**
     * 获取全部分类信息
     */
    @GET("api/v4/categories/all")
    fun getAllCategoriesInfo(): Observable<AndyInfo>

    /**
     * 获取排行榜tab信息
     */
    @GET("api/v4/rankList")
    fun getRankListTab(): Observable<Tab>

    /**
     * 获取专题信息
     */
    @GET("api/v3/specialTopics")
    fun getTopicInfo(): Observable<AndyInfo>

    /**
     * 获取tag信息
     * @param tagId tagId
     * @param strategy tag模式
     */
    @GET("api/v3/tag/videos")
    fun getTagInfo(@Query("tagId") tagId: String, @Query("strategy") strategy: String): Observable<AndyInfo>

    /**
     * 获取种类下tab详细信息
     */
    @GET("api/v4/categories/detail/tab")
    fun getCategoryTabInfo(@Query("id") id: String): Observable<Category>

    /**
     * 获取种类下tab下列表集合
     */
    @GET("api/v4/categories/detail/index")
    fun getCategroyTabListItemInfo(@Query("id") id: String): Observable<AndyInfo>

    ///////////////////////////////////////////////////////////////////////////
    // 关注相关
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 关注
     */
    @GET("api/v4/tabs/follow")
    fun getFollowInfo(): Observable<AndyInfo>

    /**
     * 全部作者
     */
    @GET("api/v4/pgcs/all")
    fun getAllAuthor(): Observable<AndyInfo>

    /**
     * 作者详细信息
     */
    @GET("api/v4/pgcs/detail/tab")
    fun getAuthorTagDetail(@Query("id") id: String): Observable<Tab>

    ///////////////////////////////////////////////////////////////////////////
    // 公共接口
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 根据url,获取数据
     * @url tab请求地址
     */
    @GET
    fun getDataInfoFromUrl(@Url url: String?): Observable<AndyInfo>

    /**
     * 根据url,获取更多信息
     * @param url 下一页请求地址
     */
    @GET
    fun getMoreAndyInfo(@Url url: String?): Observable<AndyInfo>


    /**
     * 根据url,获取更多信息
     * @param url 下一页请求地址
     */
    @GET
    fun getMoreJenniferInfo(@Url url: String?): Observable<JenniferInfo>


    ///////////////////////////////////////////////////////////////////////////
    // 视频相关
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 根据视频id,获取相关信息
     */
    @GET("api/v2/video/{id}")
    fun getVideoInfoById(@Path("id") id: String): Observable<ContentBean>

    /**
     * 获取相关视频信息
     * @id 视频id
     */
    @GET("api/v4/video/related")
    fun getRelatedVideo(@Query("id") id: String): Observable<AndyInfo>

    /**
     * 每日编辑精选
     */
    @GET("api/v2/feed?num=3")
    fun getDailyElite(): Observable<JenniferInfo>


    /**
     * 下载视频
     */
    @Streaming
    @GET
    fun downloadVideo(@Url url: String): Call<ResponseBody>


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/Extras.kt
================================================
package com.jennifer.andy.simpleeyes.net


/**
 * Author:  andy.xwt
 * Date:    2017/10/31 14:16
 * Description:
 */

object Extras {

    /**
     * 更新参数
     */
    const val UPDATE_PARAMS = "update_params"
    const val VIDEO_INFO = "video_info"
    const val VIDEO_INFO_INDEX = "video_info_index"
    const val VIDEO_LIST_INFO = "video_list_info"
    const val SLOGAN_ENGLISH = "slogan_english"
    const val SLOGAN_CHINESE = "slogan_chinese"
    const val API_URL = "api_url"
    const val ID = "id"
    const val TAB_INDEX = "tab_index"
    const val TITLE = "title"

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/net/RetrofitConfig.kt
================================================
package com.jennifer.andy.simpleeyes.net

import com.google.gson.GsonBuilder
import com.jennifer.andy.simpleeyes.AndyApplication
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.util.concurrent.TimeUnit


/**
 * Author:  andy.xwt
 * Date:    2017/10/10 22:41
 * Description:
 */
class RetrofitConfig private constructor() {


    private lateinit var mRetrofit: Retrofit
    private lateinit var mHttpLoggingInterceptor: HttpLoggingInterceptor
    private lateinit var mRequestInterceptor: Interceptor

    private lateinit var mOkHttpClient: OkHttpClient
    private lateinit var mCache: Cache

    companion object {

        private const val READ_TIME_OUT = 5000L// 读取超过时间 单位:毫秒
        private const val WRITE_TIME_OUT = 5000L//写超过时间 单位:毫秒
        private const val FILE_CACHE_SIZE = 1024 * 1024 * 100L//缓存大小100Mb
        private val Instance: RetrofitConfig by lazy { RetrofitConfig() }

        /**
         * 获取默认请求接口
         */
        @JvmStatic
        fun getDefaultService(): ApiService {
            return Instance.apiService
        }

    }


    private lateinit var apiService: ApiService

    init {
        initRequestInterceptor()
        initLoggingInterceptor()
        initCachePathAndSize()
        initOkHttpClient()
        initRetrofit()
    }

    /**
     * 初始化请求拦截,添加缓存头,与全局请求参数
     */
    private fun initRequestInterceptor() {
        mRequestInterceptor = Interceptor { chain ->
            //注意全局请求参数都是死的
            val urlBuilder = chain.request().url()
                    .newBuilder()
                    .setEncodedQueryParameter("udid", "d0f6190461864a3a978bdbcb3fe9b48709f1f390")
                    .setEncodedQueryParameter("vc", "225")
                    .setEncodedQueryParameter("vn", "3.12.0")
                    .setEncodedQueryParameter("deviceModel", "Redmi%204")
                    .setEncodedQueryParameter("first_channel", "eyepetizer_xiaomi_market")
                    .setEncodedQueryParameter("last_channel", "eyepetizer_xiaomi_market")
                    .setEncodedQueryParameter("system_version_code", "23")

            val request = chain.request().newBuilder()
                    .addHeader("Content-Type", "application/json")
                    .addHeader("Cookie", "ky_auth=;sdk=23")
                    .addHeader("model", "Android")
                    .url(urlBuilder.build())
                    .build()

            chain.proceed(request)
        }
    }

    /**
     * 初始化日志拦截
     */
    private fun initLoggingInterceptor() {
        mHttpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
    }

    /**
     * 配置缓存大小与缓存地址
     */
    private fun initCachePathAndSize() {
        val cacheFile = File(AndyApplication.INSTANCE.cacheDir, "cache")
        mCache = Cache(cacheFile, FILE_CACHE_SIZE)
    }


    /**
     * 配置okHttp
     */
    private fun initOkHttpClient() {
        mOkHttpClient = OkHttpClient.Builder()
                .readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
                .writeTimeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
                .cache(mCache)
                .addInterceptor(mRequestInterceptor)
                .addInterceptor(mHttpLoggingInterceptor)
                .build()
    }

    /**
     * 配置retrofit
     */
    private fun initRetrofit() {
        val gson = GsonBuilder()
                .setDateFormat("yyyy-MM-dd hh:mm:ss")
                .create()
        mRetrofit = Retrofit.Builder()
                .client(mOkHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(Api.BASE_URL)
                .build()
        apiService = mRetrofit.create(ApiService::class.java)

    }


}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/FileMediaDataSource.kt
================================================
package com.jennifer.andy.simpleeyes.player

import tv.danmaku.ijk.media.player.misc.IMediaDataSource
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile


/**
 * Author:  andy.xwt
 * Date:    2020/3/10 13:29
 * Description:
 */

class FileMediaDataSource(file: File) : IMediaDataSource {

    private var mFile: RandomAccessFile = RandomAccessFile(file, "r")
    var mFileSize = mFile.length()

    @Throws(IOException::class)
    override fun readAt(position: Long, buffer: ByteArray?, offset: Int, size: Int): Int {
        if (mFile.filePointer != position) mFile.seek(position)
        return if (size == 0) 0 else mFile.read(buffer, 0, size)
    }

    @Throws(IOException::class)
    override fun getSize(): Long {
        return mFileSize
    }

    @Throws(IOException::class)
    override fun close() {
        mFileSize = 0
        mFile.close()
    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkMediaController.kt
================================================
package com.jennifer.andy.simpleeyes.player

import android.app.Activity
import android.content.Context
import android.graphics.PixelFormat
import android.media.AudioManager
import android.util.Log
import android.view.*
import android.view.View.OnLayoutChangeListener
import android.view.View.OnTouchListener
import android.widget.FrameLayout
import android.widget.MediaController.MediaPlayerControl
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.ContentBean
import com.jennifer.andy.simpleeyes.player.event.VideoProgressEvent
import com.jennifer.andy.simpleeyes.player.view.ControllerView
import com.jennifer.andy.simpleeyes.player.view.ControllerViewFactory


/**
 * Author:  andy.xwt
 * Date:    2020/3/10 17:38
 * Description:IjkMediaController 用于视频播放的UI,内部原理是通过window进行展示,其中window的位置与大小
 * 是根据[mAnchor]的位置动态调整的。
 *
 */

class IjkMediaController(context: Context) : FrameLayout(context) {

    private lateinit var player: MediaPlayerControl

    private lateinit var windowManager: WindowManager
    private lateinit var window: Window
    private lateinit var decorLayoutParams: WindowManager.LayoutParams
    private lateinit var decorView: View

    private lateinit var mAnchor: View//锚点view,用于更新window的位置

    var isShowing = false//当前window是否展示

    var currentVideoInfo: ContentBean? = null
    var totalCount = 0
    var currentIndex = 0

    private var showMode1 = ControllerViewFactory.TINY_MODE //当前window展示的类型,默认情况下是小视图

    var controllerListener: ControllerListener? = null
    var controllerView: ControllerView? = null

    private val mControllerViewFactory = ControllerViewFactory()

    companion object {
        private const val WINDOW_TIME_OUT = 3500L //默认window消失时间 3.5秒
    }

    init {
        initFloatingWindowLayout()
        initFloatingWindowConfig()
    }

    /**
     * 初始化window布局
     */
    private fun initFloatingWindowLayout() {
        decorLayoutParams = WindowManager.LayoutParams().apply {
            gravity = Gravity.TOP or Gravity.LEFT
            height = LayoutParams.WRAP_CONTENT
            x = 0
            format = PixelFormat.TRANSLUCENT
            type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
            flags = flags or (WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
                    or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
            token = null
            windowAnimations = 0
        }

    }

    /**
     * 初始化window参数
     */
    private fun initFloatingWindowConfig() {
        windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        window = PolicyCompat().createPhoneWindow(context).apply {
            setWindowManager(windowManager, null, null)
            requestFeature(Window.FEATURE_NO_TITLE)
            setContentView(this@IjkMediaController)
            setBackgroundDrawableResource(R.color.transparent)
            volumeControlStream = AudioManager.STREAM_MUSIC
        }

        decorView = window.decorView
        decorView.setOnTouchListener(mTouchListener)
        isFocusable = true
        isFocusableInTouchMode = true
        descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS
        requestFocus()
    }


    /**
     * 初始化数据
     *
     * @param currentIndex     当前视频角标
     * @param totalCount       视频集合总数
     * @param currentVideoInfo 当前视频信息
     */
    fun initData(currentIndex: Int, totalCount: Int, currentVideoInfo: ContentBean?) {
        this.currentIndex = currentIndex
        this.totalCount = totalCount
        this.currentVideoInfo = currentVideoInfo
    }

    /**
     * 当锚点布局发生改变时,动态更新window的位置,高度与宽度
     */
    private fun updateFloatingWindowLayout() {
        val anchorPos = IntArray(2)
        mAnchor.getLocationOnScreen(anchorPos)

        //重新测量
        decorView.measure(MeasureSpec.makeMeasureSpec(mAnchor.width, MeasureSpec.AT_MOST),
                MeasureSpec.makeMeasureSpec(mAnchor.height, MeasureSpec.AT_MOST))

        decorLayoutParams.apply {
            width = mAnchor.width
            x = anchorPos[0]
            y = anchorPos[1]
            height = mAnchor.height
        }

    }

    fun setMediaPlayer(player: MediaPlayerControl) {
        this.player = player
        switchControllerView(showMode1)
    }

    /**
     * 当锚点view布局发生改变的时,重新设置当前window的布局参数
     */
    private val mLayoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
        updateFloatingWindowLayout()
        if (isShowing) windowManager.updateViewLayout(decorView, decorLayoutParams)

    }

    /**
     * 将控制层view与锚点的关联
     */
    fun setAnchorView(view: View) {
        mAnchor = view
        mAnchor.addOnLayoutChangeListener(mLayoutChangeListener)
        updateFloatingWindowLayout()
    }

    ///////////////////////////////////////////////////////////////////////////
    // Window显示相关
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 触摸监听,如果当前window正在显示,再次按下走默认时间内该window会消失
     */
    private val mTouchListener = OnTouchListener { _, event ->
        if (event.action == MotionEvent.ACTION_DOWN) {
            if (isShowing) {
                removeCallbacks(mFadeOut)
                postDelayed(mFadeOut, WINDOW_TIME_OUT)
            }
        }
        false
    }

    /**
     * 隐藏控制层view
     */
    private val mFadeOut = Runnable {
        if (isShowing) hide()
    }

    /**
     * 第一次显示,不显示控制层
     */
    fun firstShow() {
        if (!isShowing && controllerView != null) {
            controllerView?.display()
        }
    }

    /**
     * 将控制器显示在屏幕上,当到达过期时间时会自动消失。
     *
     * @param timeout 过期时间(毫秒) 如果设置为0,那么会直到调用hide方法才会消失
     */
    @JvmOverloads
    fun show(timeout: Long = WINDOW_TIME_OUT) {
        if (!isShowing) {
            windowManager.addView(decorView, decorLayoutParams)
            isShowing = true
        }
        if (timeout != 0L) {
            removeCallbacks(mFadeOut)
            postDelayed(mFadeOut, timeout.toLong())
        }
        controllerView?.display()
        controllerListener?.onShowController(true)

    }

    /**
     * 隐藏window
     */
    fun hide() {
        try {
            removeCallbacks(mFadeOut)
            isShowing = false
            controllerListener?.onShowController(false)
            windowManager.removeView(decorView)
        } catch (ex: IllegalArgumentException) {
            Log.w("MediaController", "already removed")
        }
    }

    /**
     * 一直显示window
     */
    fun showAllTheTime() {
        show(3600000)
        removeCallbacks(mFadeOut)
    }

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        val keyCode = event.keyCode
        val uniqueDown = (event.repeatCount == 0 && event.action == KeyEvent.ACTION_DOWN)
        if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
            if (uniqueDown) {
                hide()
                //如果传入的是activity直接退出
                if (context is Activity) (context as Activity).finish()

            }
            return true
        }
        return super.dispatchKeyEvent(event)
    }

    ///////////////////////////////////////////////////////////////////////////
    // Window显示内容相关
    ///////////////////////////////////////////////////////////////////////////


    /**
     * 根据显示模式切换展示的控制UI
     *
     * @param showMode
     */
    fun switchControllerView(showMode: Int) {
        showMode1 = showMode
        removeAllViews()
        controllerView = mControllerViewFactory.create(showMode, context)
        controllerView?.initControllerAndData(player, this, currentVideoInfo)
        controllerView?.display()
        addView(controllerView)
    }

    /**
     * 显示错误布局
     */
    fun showErrorView() {
        if (!isShowing) {
            controllerView?.showErrorView()
            windowManager.addView(decorView, decorLayoutParams)
            isShowing = true
        }
    }

    fun updateProgressAndTime(videoProgressEvent: VideoProgressEvent?) {
        controllerView?.updateProgressAndTime(videoProgressEvent!!)
    }

    fun resetType() {
        controllerView?.showContent()
    }

    ///////////////////////////////////////////////////////////////////////////
    // 数据相关
    ///////////////////////////////////////////////////////////////////////////


    /**
     * 判断是否拥有上一个视频
     */
    fun isHavePreVideo() = totalCount > 0 && currentIndex > 0


    /**
     * 是否拥有下一个视频
     */
    fun isHaveNextVideo() = totalCount > 0 && currentIndex < totalCount - 1


    ///////////////////////////////////////////////////////////////////////////
    // 回调监听
    ///////////////////////////////////////////////////////////////////////////

    interface ControllerListener {
        //退出点击
        fun onBackClick()

        //上一页点击
        fun onPreClick()

        //下一页点击
        fun onNextClick()

        //全屏点击
        fun onFullScreenClick()

        //退出全屏
        fun onTinyScreenClick()

        //错误界面点击
        fun onErrorViewClick()

        //是否显示控制层
        fun onShowController(isShowController: Boolean)
    }


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkVideoView.kt
================================================
package com.jennifer.andy.simpleeyes.player

import android.annotation.TargetApi
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.MediaController.MediaPlayerControl
import com.jennifer.andy.simpleeyes.player.event.VideoProgressEvent
import com.jennifer.andy.simpleeyes.player.render.IRenderView
import com.jennifer.andy.simpleeyes.player.render.IRenderView.IRenderCallback
import com.jennifer.andy.simpleeyes.player.render.IRenderView.ISurfaceHolder
import com.jennifer.andy.simpleeyes.player.render.SurfaceRenderView
import com.jennifer.andy.simpleeyes.player.render.TextureRenderView
import com.jennifer.andy.simpleeyes.player.view.ControllerViewFactory
import com.jennifer.andy.simpleeyes.rx.RxBus
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import tv.danmaku.ijk.media.player.misc.IMediaDataSource
import java.io.File
import java.io.IOException
import java.util.*
import java.util.concurrent.TimeUnit


/**
 * Author:  andy.xwt
 * Date:    2020/3/12 23:03
 * Description:
 */

class IjkVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : FrameLayout(context, attrs, defStyleAttr), MediaPlayerControl {

    private var mUri: Uri? = null
    private var mHeaders: Map<String, String>? = null

    private var mSurfaceHolder: ISurfaceHolder? = null
    private var mMediaPlayer: IMediaPlayer? = null
    private var mVideoWidth = 0
    private var mVideoHeight = 0
    private var mSurfaceWidth = 0
    private var mSurfaceHeight = 0

    private var mRenderView: IRenderView? = null
    private var mCurrentRender = RENDER_NONE
    private var mRenderUIView: View? = null

    private var mVideoRotationDegree = 0
    private var mVideoSarNum = 0
    private var mVideoSarDen = 0
    private var mCurrentAspectRatio = allAspectRatio[0]
    private var mMediaController: IjkMediaController? = null


    private var mOnCompletionListener: IMediaPlayer.OnCompletionListener? = null
    private var mOnPreparedListener: IMediaPlayer.OnPreparedListener? = null
    private var mOnErrorListener: IMediaPlayer.OnErrorListener? = null
    private var mOnInfoListener: IMediaPlayer.OnInfoListener? = null

    private var mCurrentBufferPercentage = 0
    private var mSeekWhenPrepared = 0

    private val mCanPause = true
    private val mCanSeekBack = true
    private val mCanSeekForward = true

    private lateinit var mAppContext: Context


    private var mCurrentState = STATE_IDLE
    private var mTargetState = STATE_IDLE
    var screenState = ControllerViewFactory.TINY_MODE


    private val mSHCallback: IRenderCallback = object : IRenderCallback {
        override fun onSurfaceChanged(holder: ISurfaceHolder, format: Int, w: Int, h: Int) {
            if (holder.renderView !== mRenderView) {
                Log.e(TAG, "onSurfaceChanged: unmatched render callback\n")
                return
            }
            mSurfaceWidth = w
            mSurfaceHeight = h
            val isValidState = mTargetState == STATE_PLAYING
            val hasValidSize = !mRenderView!!.shouldWaitForResize() || mVideoWidth == w && mVideoHeight == h
            if (mMediaPlayer != null && isValidState && hasValidSize) {
                if (mSeekWhenPrepared != 0) {
                    seekTo(mSeekWhenPrepared)
                }
                start()
            }
        }

        override fun onSurfaceCreated(holder: ISurfaceHolder, width: Int, height: Int) {
            if (holder.renderView !== mRenderView) {
                Log.e(TAG, "onSurfaceCreated: unmatched render callback\n")
                return
            }
            mSurfaceHolder = holder
            if (mMediaPlayer != null) bindSurfaceHolder(mMediaPlayer, holder) else openVideo()
        }

        override fun onSurfaceDestroyed(holder: ISurfaceHolder) {
            if (holder.renderView !== mRenderView) {
                Log.e(TAG, "onSurfaceDestroyed: unmatched render callback\n")
                return
            }
            // after we return from this we can't use the surface any more
            mSurfaceHolder = null
            // REMOVED: if (mMediaController != null) mMediaController.hide();
            // REMOVED: release(true);
            releaseWithoutStop()
        }
    }

    companion object {

        private const val STATE_ERROR = -1
        private const val STATE_IDLE = 0
        private const val STATE_PREPARING = 1
        private const val STATE_PREPARED = 2
        private const val STATE_PLAYING = 3
        private const val STATE_PAUSED = 4
        private const val STATE_PLAYBACK_COMPLETED = 5

        private val allAspectRatio = intArrayOf(
                IRenderView.AR_ASPECT_FIT_PARENT,
                IRenderView.AR_ASPECT_FILL_PARENT,
                IRenderView.AR_ASPECT_WRAP_CONTENT,
                IRenderView.AR_16_9_FIT_PARENT,
                IRenderView.AR_4_3_FIT_PARENT)
        const val RENDER_NONE = 0
        const val RENDER_SURFACE_VIEW = 1
        const val RENDER_TEXTURE_VIEW = 2
        private const val TAG = "IjkVideoView"
    }

    init {
        initVideoView(context)
    }

    fun toggleAspectRatio(currentAspectRatio: Int): Int {
        mRenderView?.setAspectRatio(currentAspectRatio)
        mCurrentAspectRatio = currentAspectRatio
        return mCurrentAspectRatio
    }

    private fun initVideoView(context: Context) {
        mAppContext = context.applicationContext
        initRenders()
        //初始化宽高
        mVideoWidth = 0
        mVideoHeight = 0
        //获取焦点
        isFocusable = true
        isFocusableInTouchMode = true
        requestFocus()
        //初始化状态
        mCurrentState = STATE_IDLE
        mTargetState = STATE_IDLE
        //设置背景
        background = ColorDrawable(Color.BLACK)
    }

    /**
     * 设置渲染Render
     * 4.0 及以上版本使用 TextureView
     * 4.0 版本以下使用 SurfaceView
     */
    private fun initRenders() {
        mCurrentRender = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { //4.0以上
            RENDER_TEXTURE_VIEW
        } else {
            RENDER_SURFACE_VIEW
        }
        setRender(mCurrentRender)
    }

    /**
     * 设置渲染Render
     */
    private fun setRender(render: Int) {
        when (render) {
            RENDER_NONE -> setRenderView(null)
            RENDER_TEXTURE_VIEW -> {
                //TextureView
                val renderView = TextureRenderView(context)
                if (mMediaPlayer != null) {
                    renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer)
                    renderView.setVideoSize(mMediaPlayer!!.videoWidth, mMediaPlayer!!.videoHeight)
                    renderView.setVideoSampleAspectRatio(mMediaPlayer!!.videoSarNum, mMediaPlayer!!.videoSarDen)
                    renderView.setAspectRatio(mCurrentAspectRatio)
                }
                setRenderView(renderView)
            }
            RENDER_SURFACE_VIEW -> {
                //surfaceView
                val renderView = SurfaceRenderView(context)
                setRenderView(renderView)
            }
            else -> Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render))
        }
    }


    /**
     * 设置渲染界面,初始化当前的渲染界面参数,并设置渲染回调等初始化操作
     */
    private fun setRenderView(renderView: IRenderView?) {
        if (mRenderView != null) {
            if (mMediaPlayer != null)
                mMediaPlayer?.setDisplay(null)
            val renderUIView = mRenderView?.getView()
            mRenderView?.removeRenderCallback(mSHCallback)
            mRenderView = null
            removeView(renderUIView)
        }
        if (renderView == null) return
        mRenderView = renderView
        renderView.setAspectRatio(mCurrentAspectRatio) //设置适配比例
        if (mVideoWidth > 0 && mVideoHeight > 0)
            renderView.setVideoSize(mVideoWidth, mVideoHeight) //设置视频的宽高
        if (mVideoSarNum > 0 && mVideoSarDen > 0) //设置采样比例
            renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen)

        mRenderUIView = mRenderView?.getView().apply {
            layoutParams = LayoutParams(
                    LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT,
                    Gravity.CENTER)
        }
        addView(mRenderUIView)
        mRenderView?.addRenderCallback(mSHCallback) //添加回调
        mRenderView?.setVideoRotation(mVideoRotationDegree) //设置视频旋转
    }

    /**
     * 设置视频播放路径
     *
     * @param path 视频路径
     */
    fun setVideoPath(path: String?) {
        setVideoURI(Uri.parse(path))
    }

    /**
     * 设置视频URI
     *
     * @param uri 视频URI
     */
    fun setVideoURI(uri: Uri) {
        setVideoURI(uri, null)
    }

    /**
     * 设置带header 的 视频URI
     *
     *
     * 注意,默认情况下允许跨域重定向,
     * 但可以通过以“android allow cross domain redirect”为键,
     * 以“0”或“1”为值的headers,以禁止或允许跨域重定向。
     *
     * @param uri     视频URI.
     * @param headers URI请求需要的header
     */
    private fun setVideoURI(uri: Uri, headers: Map<String, String>?) {
        mUri = uri
        mHeaders = headers
        mSeekWhenPrepared = 0
        openVideo()
        requestLayout()
        invalidate()
    }

    fun stopPlayback() {
        mMediaPlayer?.let {
            mMediaPlayer?.stop()
            mMediaPlayer?.release()
            mMediaPlayer = null
            mCurrentState = STATE_IDLE
            mTargetState = STATE_IDLE

            mMediaController?.let {
                mMediaController?.hide()
                mMediaController = null
            }

            val am = mAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
            am.abandonAudioFocus(null)
            cancelProgressRunnable()
        }
    }

    /**
     * 打开视频
     */
    @TargetApi(Build.VERSION_CODES.M)
    private fun openVideo() {
        if (mUri == null || mSurfaceHolder == null) { // not ready for playback just yet, will try again later
            return
        }
        // we shouldn't clear the target state, because somebody might have
        // called start() previously
        release(false)
        val am = mAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
        am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
        try {
            mMediaPlayer = createPlayer()
            mMediaPlayer?.setOnPreparedListener(mPreparedListener)
            mMediaPlayer?.setOnVideoSizeChangedListener(mSizeChangedListener)
            mMediaPlayer?.setOnCompletionListener(mCompletionListener)
            mMediaPlayer?.setOnErrorListener(mErrorListener)
            mMediaPlayer?.setOnInfoListener(mInfoListener)
            mMediaPlayer?.setOnBufferingUpdateListener(mBufferingUpdateListener)
            mMediaPlayer?.setOnSeekCompleteListener(mSeekCompleteListener)
            mCurrentBufferPercentage = 0
            val scheme = mUri?.scheme
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && (TextUtils.isEmpty(scheme)
                            || scheme.equals("file", ignoreCase = true))) {
                val dataSource: IMediaDataSource = FileMediaDataSource(File(mUri.toString()))
                mMediaPlayer?.setDataSource(dataSource)
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                mMediaPlayer?.setDataSource(mAppContext, mUri, mHeaders)
            } else {
                mMediaPlayer?.dataSource = mUri.toString()
            }
            bindSurfaceHolder(mMediaPlayer, mSurfaceHolder)
            mMediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
            mMediaPlayer?.setScreenOnWhilePlaying(true)
            mMediaPlayer?.prepareAsync()
            mCurrentState = STATE_PREPARING
            attachMediaController()
        } catch (ex: IOException) {
            Log.w(TAG, "Unable to open content: $mUri", ex)
            mCurrentState = STATE_ERROR
            mTargetState = STATE_ERROR
            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0)
        } catch (ex: IllegalArgumentException) {
            Log.w(TAG, "Unable to open content: $mUri", ex)
            mCurrentState = STATE_ERROR
            mTargetState = STATE_ERROR
            mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0)
        } finally { // REMOVED: mPendingSubtitleTracks.clear();
        }
    }

    fun setMediaController(controller: IjkMediaController?) {
        mMediaController = controller
        attachMediaController()
    }

    fun getMediaController(): IjkMediaController? {
        return mMediaController
    }

    private fun attachMediaController() {
        if (mMediaPlayer != null && mMediaController != null) {
            mMediaController?.setMediaPlayer(this)
            val anchorView = if (this.parent is View) this.parent as View else this
            mMediaController?.setAnchorView(anchorView)
            mMediaController?.isEnabled = isInPlaybackState()
        }
    }

    var mSizeChangedListener = IMediaPlayer.OnVideoSizeChangedListener { mp, _, _, _, _ ->
        mVideoWidth = mp.videoWidth
        mVideoHeight = mp.videoHeight
        mVideoSarNum = mp.videoSarNum
        mVideoSarDen = mp.videoSarDen
        if (mVideoWidth != 0 && mVideoHeight != 0) {
            mRenderView?.let {
                mRenderView?.setVideoSize(mVideoWidth, mVideoHeight)
                mRenderView?.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen)
            }
            // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
            requestLayout()
        }
    }
    /**
     * 视频播放准备完成监听
     */
    var mPreparedListener = IMediaPlayer.OnPreparedListener { mp ->
        mCurrentState = STATE_PREPARED
        if (mOnPreparedListener != null) {
            mOnPreparedListener!!.onPrepared(mMediaPlayer)
        }
        if (mMediaController != null) {
            mMediaController!!.isEnabled = true
        }
        mVideoWidth = mp.videoWidth
        mVideoHeight = mp.videoHeight
        val seekToPosition = mSeekWhenPrepared // mSeekWhenPrepared may be changed after seekTo() call
        if (seekToPosition != 0) {
            seekTo(seekToPosition)
        }
        if (mVideoWidth != 0 && mVideoHeight != 0) {
            mRenderView?.let {
                mRenderView?.setVideoSize(mVideoWidth, mVideoHeight)
                mRenderView?.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen)
                if (!mRenderView!!.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
                    // We didn't actually change the size (it was already at the size
                    // we need), so we won't get a "surface changed" callback, so
                    // start the video here instead of in the callback.
                    if (mTargetState == STATE_PLAYING) {
                        start()
                        mMediaController?.firstShow()
                    } else if (!isPlaying && (seekToPosition != 0 || currentPosition > 0)) {
                        // Show the media controls when we're paused into a video and make 'em stick.
                        mMediaController?.show(0)
                    }
                }
            }
        } else { // We don't know the video size yet, but should start anyway.
            // The video size might be reported to us later.
            if (mTargetState == STATE_PLAYING) {
                start()
            }
        }
    }
    private val mCompletionListener = IMediaPlayer.OnCompletionListener {
        mCurrentState = STATE_PLAYBACK_COMPLETED
        mTargetState = STATE_PLAYBACK_COMPLETED
        mMediaController?.hide()
        mOnCompletionListener?.onCompletion(mMediaPlayer)
    }
    private val mInfoListener = IMediaPlayer.OnInfoListener { mp, arg1, arg2 ->
        mOnInfoListener?.onInfo(mp, arg1, arg2)
        when (arg1) {
            IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING -> Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:")
            IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START -> Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:")
            IMediaPlayer.MEDIA_INFO_BUFFERING_START -> Log.d(TAG, "MEDIA_INFO_BUFFERING_START:")
            IMediaPlayer.MEDIA_INFO_BUFFERING_END -> Log.d(TAG, "MEDIA_INFO_BUFFERING_END:")
            IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH -> Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: $arg2")
            IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING -> Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:")
            IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE -> Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:")
            IMediaPlayer.MEDIA_INFO_METADATA_UPDATE -> Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:")
            IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE -> Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:")
            IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT -> Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:")
            IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED -> {
                mVideoRotationDegree = arg2
                Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: $arg2")
                if (mRenderView != null) mRenderView!!.setVideoRotation(arg2)
            }
            IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START -> Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:")
        }
        true
    }
    private val mErrorListener = IMediaPlayer.OnErrorListener { mp, framework_err, impl_err ->
        Log.d(TAG, "Error: $framework_err,$impl_err")
        mCurrentState = STATE_ERROR
        mTargetState = STATE_ERROR
        /* If an error handler has been supplied, use it and finish. */if (mOnErrorListener != null) {
        if (mOnErrorListener!!.onError(mMediaPlayer, framework_err, impl_err)) {
            return@OnErrorListener true
        }
    }
        true
    }
    private val mBufferingUpdateListener = IMediaPlayer.OnBufferingUpdateListener { _, percent ->
        mCurrentBufferPercentage = percent
    }
    private val mSeekCompleteListener = IMediaPlayer.OnSeekCompleteListener { }

    /**
     * Register a callback to be invoked when the media file
     * is loaded and ready to go.
     *
     * @param l The callback that will be run
     */
    fun setOnPreparedListener(l: IMediaPlayer.OnPreparedListener?) {
        mOnPreparedListener = l
    }

    /**
     * Register a callback to be invoked when the end of a media file
     * has been reached during playback.
     *
     * @param l The callback that will be run
     */
    fun setOnCompletionListener(l: IMediaPlayer.OnCompletionListener?) {
        mOnCompletionListener = l
    }

    /**
     * Register a callback to be invoked when an error occurs
     * during playback or setup.  If no listener is specified,
     * or if the listener returned false, VideoView will inform
     * the user of any errors.
     *
     * @param l The callback that will be run
     */
    fun setOnErrorListener(l: IMediaPlayer.OnErrorListener?) {
        mOnErrorListener = l
    }

    /**
     * Register a callback to be invoked when an informational event
     * occurs during playback or setup.
     *
     * @param l The callback that will be run
     */
    fun setOnInfoListener(l: IMediaPlayer.OnInfoListener?) {
        mOnInfoListener = l
    }

    // REMOVED: mSHCallback
    private fun bindSurfaceHolder(mp: IMediaPlayer?, holder: ISurfaceHolder?) {
        if (mp == null) return
        if (holder == null) {
            mp.setDisplay(null)
            return
        }
        holder.bindToMediaPlayer(mp)
    }

    fun releaseWithoutStop() {
        if (mMediaPlayer != null) mMediaPlayer!!.setDisplay(null)
    }

    /*
     * release the media player in any state
     */
    fun release(clearTargetState: Boolean) {
        if (mMediaPlayer != null) {
            mMediaPlayer!!.reset()
            mMediaPlayer!!.release()
            mMediaPlayer = null
            mMediaController!!.hide()
            if (clearTargetState) {
                mTargetState = STATE_IDLE
            }
            setRenderView(null)
            initRenders()
            val am = mAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
            am.abandonAudioFocus(null)
            cancelProgressRunnable()
        }
    }

    override fun onTrackballEvent(ev: MotionEvent?): Boolean {
        if (isInPlaybackState() && mMediaController != null) {
            toggleMediaControlsVisible()
        }
        return false
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        val isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && keyCode != KeyEvent.KEYCODE_MENU && keyCode != KeyEvent.KEYCODE_CALL && keyCode != KeyEvent.KEYCODE_ENDCALL
        if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
            if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
                    keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
                if (mMediaPlayer!!.isPlaying) {
                    pause()
                    mMediaController!!.show()
                } else {
                    start()
                    mMediaController!!.hide()
                }
                return true
            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
                if (!mMediaPlayer!!.isPlaying) {
                    start()
                    mMediaController!!.hide()
                }
                return true
            } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
                    || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
                if (mMediaPlayer!!.isPlaying) {
                    pause()
                    mMediaController!!.show()
                }
                return true
            } else {
                toggleMediaControlsVisible()
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    fun toggleMediaControlsVisible() {

        if (mMediaController!!.isShowing) {
            mMediaController?.hide()
        } else {
            mMediaController?.show()
        }
    }

    private var mDisposable: Disposable? = null


    /**
     * 进度与时间更新线程
     */
    private fun startProgressRunnable() {
        if (mDisposable == null || mDisposable!!.isDisposed) {
            mDisposable = Observable.interval(1, TimeUnit.SECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe {
                        val position = currentPosition
                        val duration = duration
                        if (duration >= 0 && bufferPercentage > 0) {
                            val progress = 1000L * position / duration
                            val secondProgress = bufferPercentage * 10
                            //发送进度
                            val event = VideoProgressEvent(duration, position, progress.toInt(), secondProgress)
                            //这里在更新进度条时,有可能在切换控制器,所以这里要排除空的情况
                            if (mMediaController != null) {
                                mMediaController!!.updateProgressAndTime(event)
                            }
                            RxBus.post(event)
                        }
                    }
        }
    }

    /**
     * 取消进度与时间更新线程
     */
    private fun cancelProgressRunnable() {
        if (mDisposable != null && !mDisposable!!.isDisposed) {
            mDisposable?.dispose()
            mDisposable = null
        }
    }

    override fun start() {
        if (isInPlaybackState()) {
            mMediaPlayer?.start()
            mCurrentState = STATE_PLAYING
            startProgressRunnable()
        }
        mTargetState = STATE_PLAYING
    }

    override fun pause() {
        if (isInPlaybackState()) {
            if (mMediaPlayer!!.isPlaying) {
                mMediaPlayer?.pause()
                mCurrentState = STATE_PAUSED
                cancelProgressRunnable()
            }
        }
        mTargetState = STATE_PAUSED
    }

    fun suspend() {
        release(false)
        cancelProgressRunnable()
    }

    fun resume() {
        openVideo()
    }

    override fun getDuration(): Int {
        return if (isInPlaybackState()) {
            mMediaPlayer?.duration!!.toInt()
        } else -1
    }

    override fun getCurrentPosition(): Int {
        return if (isInPlaybackState()) {
            mMediaPlayer?.currentPosition!!.toInt()
        } else 0
    }

    override fun seekTo(msec: Int) {
        mSeekWhenPrepared = if (isInPlaybackState()) {
            mMediaPlayer?.seekTo(msec.toLong())
            0
        } else {
            msec
        }
    }

    override fun isPlaying(): Boolean {
        return isInPlaybackState() && mMediaPlayer!!.isPlaying
    }

    override fun getBufferPercentage(): Int {
        return if (mMediaPlayer != null) {
            mCurrentBufferPercentage
        } else 0
    }

    fun isInPlaybackState(): Boolean {
        return mMediaPlayer != null
                && mCurrentState != STATE_ERROR
                && mCurrentState != STATE_IDLE
                && mCurrentState != STATE_PREPARING
    }

    override fun canPause() = mCanPause


    override fun canSeekBackward() = mCanSeekBack

    override fun canSeekForward() = mCanSeekForward

    override fun getAudioSessionId(): Int {
        return 0
    }

    private fun createPlayer(): IMediaPlayer? {
        if (mUri != null) {
            return IjkMediaPlayer().apply {
                IjkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG)
                setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0)
                setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0)
                setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32.toLong())
                setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1)
                setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0)
                setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0)
                setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48)
            }
        }
        return null
    }

}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkVideoViewWrapper.kt
================================================
package com.jennifer.andy.simpleeyes.player

import android.app.Dialog
import android.content.Context
import android.content.pm.ActivityInfo
import android.graphics.drawable.ColorDrawable
import android.media.AudioManager
import android.provider.Settings
import android.provider.Settings.SettingNotFoundException
import android.util.AttributeSet
import android.view.*
import android.widget.FrameLayout
import android.widget.ProgressBar
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.player.view.ControllerViewFactory
import com.jennifer.andy.simpleeyes.utils.*
import tv.danmaku.ijk.media.player.IMediaPlayer
import kotlin.math.abs


/**
 * Author:  andy.xwt
 * Date:    2020/3/14 13:53
 * Description: IjkVideoViewWrapper 包含了对声音亮度的控制,同时代理了[IjkVideoView]的相关方法
 */

class IjkVideoViewWrapper @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : FrameLayout(context, attrs, defStyleAttr), GestureDetector.OnGestureListener {

    private val mIjkVideoView: IjkVideoView

    private val mPlaceImage: SimpleDraweeView
    private val mCenterProgress: ProgressBar
    private val mGestureDetector: GestureDetector
    private val mAudioManager: AudioManager

    private var mLightDialog: Dialog? = null
    private var mLightProgress: ProgressBar? = null
    private var mVolumeDialog: Dialog? = null
    private var mVolumeProgress: ProgressBar? = null

    private var isShowVolume = false
    private var isShowLight = false
    private var isShowPosition = false

    private val mScreenHeight: Int
    private var mParent: ViewGroup? = null


    companion object {
        private const val MIN_SCROLL = 3
    }

    init {
        LayoutInflater.from(context).inflate(R.layout.layout_ijk_wrapper, this, true)
        mGestureDetector = GestureDetector(context, this)
        mAudioManager = getContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
        mScreenHeight = getContext().getScreenHeight()

        mIjkVideoView = findViewById(R.id.video_view)
        mPlaceImage = findViewById(R.id.iv_place_image)
        mCenterProgress = findViewById(R.id.progress)
    }

    fun setPlaceImageUrl(url: String?) {
        togglePlaceImage(true)
        mPlaceImage.setImageURI(url)
    }

    fun togglePlaceImage(visibility: Boolean) {
        mPlaceImage.visibility = if (visibility) View.VISIBLE else View.GONE
        mCenterProgress.visibility = if (visibility) View.VISIBLE else View.GONE
    }

    fun hidePlaceImage() {
        mPlaceImage.handler.postDelayed({ togglePlaceImage(false) }, 500)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        val relValue = mGestureDetector.onTouchEvent(event)
        val action = event.action
        if (action == MotionEvent.ACTION_UP) { //当手指抬起的时候
            onUp()
        } else if (action == MotionEvent.ACTION_CANCEL) {
            onCancel()
        }
        return relValue
    }

    override fun onDown(e: MotionEvent?): Boolean {
        // 单击,触摸屏按下时立刻触发
        return true
    }

    override fun onShowPress(e: MotionEvent?) {
        // 短按,触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会
    }

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        // 抬起,手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势)
        if (mIjkVideoView.isInPlaybackState() && mIjkVideoView.getMediaController() != null && (!isShowVolume || !isShowLight || isShowPosition)) {
            mIjkVideoView.toggleMediaControlsVisible()
        }
        return true
    }

    // 滚动,触摸屏按下后移动
    override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
        if (mIjkVideoView.screenState == ControllerViewFactory.FULL_SCREEN_MODE) { //判断是否是全屏
            val downX = e1.x
            val actuallyDy = e2.y - e1.y
            val actuallyDx = e2.x - e1.x
            val absDy = abs(actuallyDy)
            val absDx = abs(actuallyDx)
            //左右移动
            if (absDx >= MIN_SCROLL && absDx > absDy) {
                showMoveToPositionDialog()
            }
            //上下移动
            if (abs(distanceY) >= MIN_SCROLL && absDy > absDx) {
                if (downX <= mScreenHeight * 0.5f) { //左边,改变声音
                    showVolumeDialog(distanceY)
                } else { //右边改变亮度
                    showLightDialog(distanceY)
                }
            }
        }
        return true
    }

    /**
     * 移动到相应位置
     */
    private fun showMoveToPositionDialog() {
        // TODO: 2018/2/26 xwt 移动到相应的位置
        isShowPosition = true
    }

    private fun onCancel() {
        onUp()
    }

    private fun onUp() {
        dismissLightDialog()
        dismissVolumeDialog()
    }

    //手势识别中不需要使用的方法
    override fun onLongPress(e: MotionEvent?) {}

    //手势识别中不需要使用的方法
    override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
        return false
    }

    /**
     * 显示声音控制对话框
     */
    private fun showVolumeDialog(deltaY: Float) {
        isShowVolume = true
        //记录滑动时候当前的声音
        var currentVideoVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
        val maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
        if (deltaY < 0) { //向下滑动
            currentVideoVolume--
        } else { //向下滑动
            currentVideoVolume++
        }
        //设置声音
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVideoVolume, 0)
        if (mVolumeDialog == null) {
            val view = LayoutInflater.from(context).inflate(R.layout.dialog_volume_controller, null)
            mVolumeProgress = view.findViewById(R.id.pb_volume_progress)
            mVolumeDialog = createDialogWithView(view, Gravity.START or Gravity.CENTER_VERTICAL)
        }
        if (!mVolumeDialog!!.isShowing) {
            mVolumeDialog!!.show()
        }
        //设置进度条
        val nextVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
        var volumePercent = (nextVolume * 1f / maxVolume * 100f).toInt()
        if (volumePercent > 100) {
            volumePercent = 100
        } else if (volumePercent < 0) {
            volumePercent = 0
        }
        mVolumeProgress!!.progress = volumePercent
    }

    /**
     * 隐藏声音对话框
     */
    private fun dismissVolumeDialog() {
        if (mVolumeDialog != null) {
            mVolumeDialog!!.dismiss()
            isShowVolume = false
        }
    }

    /**
     * 显示控制亮度对话框
     */
    private fun showLightDialog(deltaY: Float) {
        isShowLight = true
        var screenBrightness = 0f
        //记录滑动前的亮度
        val lp = getWindow(context)!!.attributes
        if (lp.screenBrightness < 0) {
            try {
                screenBrightness = Settings.System.getInt(context.contentResolver, Settings.System.SCREEN_BRIGHTNESS).toFloat()
            } catch (e: SettingNotFoundException) {
                e.printStackTrace()
            }
        } else {
            screenBrightness = lp.screenBrightness
        }
        if (deltaY < 0) { //向下滑动
            screenBrightness -= 0.03f
        } else { //向下滑动
            screenBrightness += 0.03f
        }
        when {
            screenBrightness >= 1 -> {
                lp.screenBrightness = 1f
            }
            screenBrightness < 0 -> {
                lp.screenBrightness = 0.1f
            }
            else -> {
                lp.screenBrightness = screenBrightness
            }
        }
        getWindow(context)!!.attributes = lp
        //设置亮度百分比
        if (mLightProgress == null) {
            val view = LayoutInflater.from(context).inflate(R.layout.dialog_light_controller, null)
            mLightProgress = view.findViewById(R.id.pb_light_progress)
            mLightDialog = createDialogWithView(view, Gravity.END or Gravity.CENTER_VERTICAL)
        }
        if (!mLightDialog!!.isShowing) {
            mLightDialog?.show()
        }
        val lightPercent = (screenBrightness * 100f).toInt()
        mLightProgress?.progress = lightPercent
    }

    private fun dismissLightDialog() {
        mLightDialog?.let {
            mLightDialog!!.dismiss()
            isShowLight = false
        }
    }

    /**
     * 根据View与位置创建dialog
     *
     * @param localView 内容布局
     * @param gravity   位置
     */
    fun createDialogWithView(localView: View?, gravity: Int): Dialog {
        return Dialog(context, R.style.VideoProgress).apply {
            setContentView(localView)
            window.apply {
                addFlags(Window.FEATURE_ACTION_BAR)
                addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
                addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
                setLayout(-2, -2)
                setBackgroundDrawable(ColorDrawable())
                val localLayoutParams = attributes
                localLayoutParams.gravity = gravity
                attributes = localLayoutParams
            }
        }
    }


    /**
     * 进入全屏
     */
    fun enterFullScreen() {
        hideActionBar(context)
        getActivity(context)!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
        val contentView = getActivity(context)!!.findViewById<ViewGroup>(Window.ID_ANDROID_CONTENT)
        mParent = parent as ViewGroup
        mParent!!.removeView(this)
        mIjkVideoView.screenState = ControllerViewFactory.FULL_SCREEN_MODE
        val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
        contentView.addView(this, params)
    }

    /**
     * 退出全屏
     */
    fun exitFullScreen() {
        showActionBar(context)
        val contentView = getActivity(context)!!.findViewById<ViewGroup>(Window.ID_ANDROID_CONTENT)
        getActivity(context)!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        contentView.removeView(this)
        mIjkVideoView.screenState = ControllerViewFactory.TINY_MODE
        val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
        mParent!!.addView(this, params)
    }


    ///////////////////////////////////////////////////////////////////////////
    // 代理IjkVideoView方法
    ///////////////////////////////////////////////////////////////////////////

    fun setVideoPath(playUrl: String) {
        mIjkVideoView.setVideoPath(playUrl)
    }

    fun start() {
        mIjkVideoView.start()
    }

    fun setMediaController(controller: IjkMediaController?) {
        mIjkVideoView.setMediaController(controller)
    }

    fun toggleAspectRatio(currentAspectRatio: Int) {
        mIjkVideoView.toggleAspectRatio(currentAspectRatio)
    }

    fun setOnPreparedListener(onPreparedListener: IMediaPlayer.OnPreparedListener) {
        mIjkVideoView.setOnPreparedListener(onPreparedListener)
    }

    fun setOnErrorListener(onErrorListener: IMediaPlayer.OnErrorListener) {
        mIjkVideoView.setOnErrorListener(onErrorListener)
    }

    fun setOnCompletionListener(onCompletionListener: IMediaPlayer.OnCompletionListener) {
        mIjkVideoView.setOnCompletionListener(onCompletionListener)
    }

    fun stopPlayback() {
        mIjkVideoView.stopPlayback()
    }

    fun isPlaying(): Boolean {
        return mIjkVideoView.isPlaying
    }

    fun pause() {
        mIjkVideoView.pause()
    }

    fun release(clearTargetState: Boolean) {
        mIjkVideoView.release(clearTargetState)
    }

    fun showErrorView(): Boolean {
        mCenterProgress.handler.postDelayed({
            mCenterProgress.visibility = View.GONE
            mIjkVideoView.getMediaController()!!.showErrorView()
        }, 1000)
        return false
    }

    fun resetType() {
        mIjkVideoView.getMediaController()?.resetType()
    }

    fun setDragging(dragging: Boolean) {
        mIjkVideoView.getMediaController()!!.controllerView!!.isDragging = dragging
    }

    fun isDragging(): Boolean {
        return mIjkVideoView.getMediaController()!!.controllerView!!.isDragging
    }

    fun showControllerAllTheTime() {
        mIjkVideoView.getMediaController()?.showAllTheTime()
    }

    fun getDuration(): Int {
        return mIjkVideoView.duration
    }

    fun showController() {
        mIjkVideoView.getMediaController()!!.show()
    }

    fun seekTo(msec: Int) {
        mIjkVideoView.seekTo(msec)
    }

}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/MeasureHelper.kt
================================================
package com.jennifer.andy.simpleeyes.player

import android.view.View
import com.jennifer.andy.simpleeyes.player.render.IRenderView
import java.lang.ref.WeakReference


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 15:09
 * Description:
 */


/**
 * 测量帮助类,根据宽高比及方向,获得测量后的宽高
 */
class MeasureHelper(view: View) {

    private val mWeakView: WeakReference<View>?
    private var mVideoWidth = 0//视频布局的宽度
    private var mVideoHeight = 0//视频布局的高度
    private var mVideoSarNum = 0 //实际视频的宽

    private var mVideoSarDen = 0//实际视频的高
    private var mVideoRotationDegree = 0 //屏幕旋转度数

    var measuredWidth = 0//测量后的宽
    var measuredHeight = 0 //测量后的高

    private var mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT //宽高比例


    val view: View? get() = mWeakView?.get()


    init {
        mWeakView = WeakReference(view)
    }

    fun setVideoSize(videoWidth: Int, videoHeight: Int) {
        mVideoWidth = videoWidth
        mVideoHeight = videoHeight
    }

    fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int) {
        mVideoSarNum = videoSarNum
        mVideoSarDen = videoSarDen
    }

    fun setVideoRotation(videoRotationDegree: Int) {
        mVideoRotationDegree = videoRotationDegree
    }

    /**
     * 当 View.onMeasure(int, int) 时调用,用于输出视频校正后的宽高
     *
     * @param widthMeasureSpec  宽度测量规则
     * @param heightMeasureSpec 高度测量规则
     */
    fun doMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {

        var widthMeasureSpec = widthMeasureSpec
        var heightMeasureSpec = heightMeasureSpec

        if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
            //如果屏幕为横屏,将宽高进行对调
            val tempSpec = widthMeasureSpec
            widthMeasureSpec = heightMeasureSpec
            heightMeasureSpec = tempSpec
        }
        //获取父布局宽高
        var width = View.getDefaultSize(mVideoWidth, widthMeasureSpec)
        var height = View.getDefaultSize(mVideoHeight, heightMeasureSpec)
        if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) {
            //如果宽高比例是匹配父布局,宽高不变
            width = widthMeasureSpec
            height = heightMeasureSpec
        }

        //设置的视频宽高大于0时
        else if (mVideoWidth > 0 && mVideoHeight > 0) {
            val widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec)
            val widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec)
            val heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec)
            val heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec)

            //当前控件测量模式为包裹模式时,根据视频比例与渲染比例,计算出视频显示的宽高比
            if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) {
                val specAspectRatio = widthSpecSize.toFloat() / heightSpecSize.toFloat()
                var displayAspectRatio: Float
                when (mCurrentAspectRatio) {
                    IRenderView.AR_16_9_FIT_PARENT -> {
                        displayAspectRatio = 16.0f / 9.0f
                        if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
                            displayAspectRatio = 1.0f / displayAspectRatio
                    }
                    IRenderView.AR_4_3_FIT_PARENT -> {
                        displayAspectRatio = 4.0f / 3.0f
                        if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
                            displayAspectRatio = 1.0f / displayAspectRatio
                    }
                    IRenderView.AR_ASPECT_FIT_PARENT,
                    IRenderView.AR_ASPECT_FILL_PARENT,
                    IRenderView.AR_ASPECT_WRAP_CONTENT -> {
                        displayAspectRatio = mVideoWidth.toFloat() / mVideoHeight.toFloat()
                        if (mVideoSarNum > 0 && mVideoSarDen > 0)
                            displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen
                    }
                    else -> {
                        displayAspectRatio = mVideoWidth.toFloat() / mVideoHeight.toFloat()
                        if (mVideoSarNum > 0 && mVideoSarDen > 0)
                            displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen
                    }
                }

                val shouldBeWider = displayAspectRatio > specAspectRatio
                when (mCurrentAspectRatio) {

                    //以下三种模式,以大的为固定边,小的边按比例缩放
                    IRenderView.AR_ASPECT_FIT_PARENT,
                    IRenderView.AR_16_9_FIT_PARENT,
                    IRenderView.AR_4_3_FIT_PARENT ->
                        if (shouldBeWider) {
                            //高度偏小,则宽度不变,对高进行校正
                            width = widthSpecSize
                            height = (width / displayAspectRatio).toInt()
                        } else {
                            //宽度偏小,则高度不变,对宽进行校正
                            height = heightSpecSize
                            width = (height * displayAspectRatio).toInt()
                        }

                    //填充模式下,以小的为固定边,大的边按比例缩放
                    IRenderView.AR_ASPECT_FILL_PARENT ->
                        if (shouldBeWider) {
                            //高度偏小,则高度不变,对宽进行校正
                            height = heightSpecSize
                            width = (height * displayAspectRatio).toInt()
                        } else {
                            //宽度偏小,则宽度不变,对高进行校正
                            width = widthSpecSize
                            height = (width / displayAspectRatio).toInt()
                        }

                    //包裹模式,以视频采样的宽高为准。
                    IRenderView.AR_ASPECT_WRAP_CONTENT ->
                        if (shouldBeWider) {
                            //高度偏小,将视频宽度与父布局宽度进行比较,取最小。并重新计算高度
                            width = mVideoWidth.coerceAtMost(widthSpecSize)
                            height = (width / displayAspectRatio).toInt()
                        } else {
                            //宽度偏小,将视频高度与父布局高度进行比较,取最小。重新计算宽度
                            height = mVideoHeight.coerceAtMost(heightSpecSize)
                            width = (height * displayAspectRatio).toInt()
                        }
                    //默认情况下
                    else -> if (shouldBeWider) {
                        width = mVideoWidth.coerceAtMost(widthSpecSize)
                        height = (width / displayAspectRatio).toInt()
                    } else {
                        height = mVideoHeight.coerceAtMost(heightSpecSize)
                        width = (height * displayAspectRatio).toInt()
                    }
                }
            }

            //视频宽高为准确模式下,以小的为固定边,大的边按比例缩放
            else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
                width = widthSpecSize
                height = heightSpecSize
                if (mVideoWidth * height < width * mVideoHeight) {// videoWidth/videoHeight < width / height
                    //宽度过大,重新调整宽度
                    width = height * mVideoWidth / mVideoHeight
                } else if (mVideoWidth * height > width * mVideoHeight) {
                    //高度过大,重新调整高度
                    height = width * mVideoHeight / mVideoWidth
                }
            }

            // 只有宽在准确模式下,宽度以实际为准,高度不能超过父布局
            else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
                width = widthSpecSize
                height = width * mVideoHeight / mVideoWidth
                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
                    height = heightSpecSize
                }
            }

            //只有高在准确模式模式下,高度以实际为准,宽度不能超过父布局
            else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
                height = heightSpecSize
                width = height * mVideoWidth / mVideoHeight
                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
                    width = widthSpecSize
                }
            }
            // 当宽高都不固定时,使用实际的视频宽高
            else {
                width = mVideoWidth
                height = mVideoHeight
                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
                    //太高,重新校正宽高
                    height = heightSpecSize
                    width = height * mVideoWidth / mVideoHeight
                }
                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
                    //太宽,重新校正宽高
                    width = widthSpecSize
                    height = width * mVideoHeight / mVideoWidth
                }
            }
        } else { // no size yet, just adopt the given spec sizes
        }
        measuredWidth = width
        measuredHeight = height
    }

    fun setAspectRatio(aspectRatio: Int) {
        mCurrentAspectRatio = aspectRatio
    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/PolicyCompat.kt
================================================
package com.jennifer.andy.simpleeyes.player

import android.content.Context
import android.view.Window


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 15:07
 * Description:
 */

private const val PHONE_WINDOW_CLASS_NAME = "com.android.internal.policy.PhoneWindow"
private const val POLICY_MANAGER_CLASS_NAME = "com.android.internal.policy.PolicyManager"


class PolicyCompat {

    /**
     * 通过反射拿不到phoneWindow,当该方法失败时,通过[makeNewWindow]
     */
    fun createPhoneWindow(context: Context): Window {
        return try {
            val clazz = Class.forName(PHONE_WINDOW_CLASS_NAME)
            val c = clazz.getConstructor(Context::class.java)
            c.newInstance(context) as Window
        } catch (e: Exception) {
            makeNewWindow(context)
        }
    }

    private fun makeNewWindow(context: Context): Window {
        return try {
            val clazz = Class.forName(POLICY_MANAGER_CLASS_NAME)
            val m = clazz.getMethod("makeNewWindow", Context::class.java)
            m.invoke(null, context) as Window
        } catch (e: Exception) {
            throw RuntimeException(e.message)
        }
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/event/VideoProgressEvent.kt
================================================
package com.jennifer.andy.simpleeyes.player.event


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 10:02 PM
 * Description: 视频播放进度事件
 */

data class VideoProgressEvent(
        var duration: Int,//总时间
        var currentPosition: Int,//当前播放时间
        var progress: Int,//第一进度(播放进度)
        var secondaryProgress: Int//第二进度(下载进度)
)


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/render/IRenderView.kt
================================================
package com.jennifer.andy.simpleeyes.player.render

import android.graphics.SurfaceTexture
import android.view.Surface
import android.view.SurfaceHolder
import android.view.View
import tv.danmaku.ijk.media.player.IMediaPlayer


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 22:44
 * Description:
 */

interface IRenderView {

    companion object {
        const val AR_ASPECT_FIT_PARENT = 0 // 适应父布局模式
        const val AR_ASPECT_FILL_PARENT = 1 //填充父布局模式
        const val AR_ASPECT_WRAP_CONTENT = 2 //包裹模式
        const val AR_MATCH_PARENT = 3 //匹配父布局模式
        const val AR_16_9_FIT_PARENT = 4 //16:9适配屏幕
        const val AR_4_3_FIT_PARENT = 5 //4:3适配屏幕
    }

    /**
     * 获取当前view
     */
    fun getView(): View?

    /**
     * 是否需要等待重新测量
     */
    fun shouldWaitForResize(): Boolean

    /**
     * 设置视屏的宽高
     */
    fun setVideoSize(videoWidth: Int, videoHeight: Int)

    /**
     * 设置视频采样方向比例
     */
    fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int)

    /**
     * 设置视频旋转角度
     */
    fun setVideoRotation(degree: Int)

    /**
     * 设置方向比例
     */
    fun setAspectRatio(aspectRatio: Int)

    /**
     * 设置渲染回调
     */
    fun addRenderCallback(callback: IRenderCallback)

    /**
     * 移除渲染回调
     */
    fun removeRenderCallback(callback: IRenderCallback)

    /**
     * surfaceHolder管理相关接口
     */
    interface ISurfaceHolder {
        /**
         * 绑定当前媒体播放器
         */
        fun bindToMediaPlayer(mp: IMediaPlayer?)

        /**
         * 获取当前渲染的view
         */
        val renderView: IRenderView

        /**
         * 获取当前surfaceHolder
         */
        val surfaceHolder: SurfaceHolder?

        /**
         * 打开surface
         */
        fun openSurface(): Surface?

        /**
         * 获取SurfaceTexture
         */
        val surfaceTexture: SurfaceTexture?
    }

    interface IRenderCallback {
        fun onSurfaceCreated(holder: ISurfaceHolder, width: Int, height: Int)
        fun onSurfaceChanged(holder: ISurfaceHolder, format: Int, width: Int, height: Int)
        fun onSurfaceDestroyed(holder: ISurfaceHolder)
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/render/SurfaceRenderView.kt
================================================
package com.jennifer.andy.simpleeyes.player.render

import android.annotation.TargetApi
import android.content.Context
import android.graphics.SurfaceTexture
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.jennifer.andy.simpleeyes.player.MeasureHelper
import com.jennifer.andy.simpleeyes.player.render.IRenderView.IRenderCallback
import com.jennifer.andy.simpleeyes.player.render.IRenderView.ISurfaceHolder
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.ISurfaceTextureHolder
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 22:47
 * Description:surfaceView渲染界面
 */

class SurfaceRenderView : SurfaceView, IRenderView {

    private lateinit var mMeasureHelper: MeasureHelper

    constructor(context: Context) : super(context) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initView(context)
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
        initView(context)
    }

    private fun initView(context: Context) {
        mMeasureHelper = MeasureHelper(this)
        mSurfaceCallback = SurfaceCallback(this)
        holder.addCallback(mSurfaceCallback)
        holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL)
    }

    override fun getView() = this

    override fun shouldWaitForResize() = true


    //--------------------
    // Layout & Measure
    //--------------------
    override fun setVideoSize(videoWidth: Int, videoHeight: Int) {
        if (videoWidth > 0 && videoHeight > 0) {
            mMeasureHelper.setVideoSize(videoWidth, videoHeight)
            holder.setFixedSize(videoWidth, videoHeight)
            requestLayout()
        }
    }

    override fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int) {
        if (videoSarNum > 0 && videoSarDen > 0) {
            mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen)
            requestLayout()
        }
    }

    override fun setVideoRotation(degree: Int) {
        Log.e("", "SurfaceView doesn't support rotation ($degree)!\n")
    }

    override fun setAspectRatio(aspectRatio: Int) {
        mMeasureHelper.setAspectRatio(aspectRatio)
        requestLayout()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(mMeasureHelper.measuredWidth, mMeasureHelper.measuredHeight)
    }

    //--------------------
    // SurfaceViewHolder
    //--------------------
    private class InternalSurfaceHolder(private val surfaceView: SurfaceRenderView,
                                        override val surfaceHolder: SurfaceHolder?) : ISurfaceHolder {

        override fun bindToMediaPlayer(mp: IMediaPlayer?) {
            if (mp != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
                        mp is ISurfaceTextureHolder) {
                    val textureHolder = mp as ISurfaceTextureHolder
                    textureHolder.surfaceTexture = null
                }
                mp.setDisplay(surfaceHolder)
            }
        }

        override val renderView: IRenderView
            get() = surfaceView

        override val surfaceTexture: SurfaceTexture?
            get() = null

        override fun openSurface(): Surface? {
            return surfaceHolder?.surface
        }

    }

    //-------------------------
    // SurfaceHolder.Callback
    //-------------------------
    override fun addRenderCallback(callback: IRenderCallback) {
        mSurfaceCallback.addRenderCallback(callback)
    }

    override fun removeRenderCallback(callback: IRenderCallback) {
        mSurfaceCallback.removeRenderCallback(callback)
    }

    private lateinit var mSurfaceCallback: SurfaceCallback

    private class SurfaceCallback(surfaceView: SurfaceRenderView) : SurfaceHolder.Callback {
        private var mSurfaceHolder: SurfaceHolder? = null
        private var mIsFormatChanged = false
        private var mFormat = 0
        private var mWidth = 0
        private var mHeight = 0
        private val mWeakSurfaceView: WeakReference<SurfaceRenderView> = WeakReference(surfaceView)
        private val mRenderCallbackMap: MutableMap<IRenderCallback, Any> = ConcurrentHashMap()

        fun addRenderCallback(callback: IRenderCallback) {
            mRenderCallbackMap[callback] = callback
            var surfaceHolder: ISurfaceHolder? = null
            if (mSurfaceHolder != null) {
                if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
                callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight)
            }
            if (mIsFormatChanged) {
                if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
                callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight)
            }
        }

        fun removeRenderCallback(callback: IRenderCallback) {
            mRenderCallbackMap.remove(callback)
        }

        override fun surfaceCreated(holder: SurfaceHolder) {
            mSurfaceHolder = holder
            mIsFormatChanged = false
            mFormat = 0
            mWidth = 0
            mHeight = 0
            //调用监听
            val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
            for (renderCallback in mRenderCallbackMap.keys) {
                renderCallback.onSurfaceCreated(surfaceHolder, 0, 0)
            }
        }

        override fun surfaceDestroyed(holder: SurfaceHolder) {
            mSurfaceHolder = null
            mIsFormatChanged = false
            mFormat = 0
            mWidth = 0
            mHeight = 0
            val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
            for (renderCallback in mRenderCallbackMap.keys) {
                renderCallback.onSurfaceDestroyed(surfaceHolder)
            }
        }

        override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                    width: Int, height: Int) {
            mSurfaceHolder = holder
            mIsFormatChanged = true
            mFormat = format
            mWidth = width
            mHeight = height
            // mMeasureHelper.setVideoSize(width, height);
            val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakSurfaceView.get()!!, mSurfaceHolder)
            for (renderCallback in mRenderCallbackMap.keys) {
                renderCallback.onSurfaceChanged(surfaceHolder, format, width, height)
            }
        }

    }

    //--------------------
    // Accessibility
    //--------------------
    override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
        super.onInitializeAccessibilityEvent(event)
        event.className = SurfaceRenderView::class.java.name
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
        super.onInitializeAccessibilityNodeInfo(info)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            info.className = SurfaceRenderView::class.java.name
        }
    }
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/render/TextureRenderView.kt
================================================
package com.jennifer.andy.simpleeyes.player.render

import android.annotation.TargetApi
import android.content.Context
import android.graphics.SurfaceTexture
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.TextureView
import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.jennifer.andy.simpleeyes.player.MeasureHelper
import com.jennifer.andy.simpleeyes.player.render.IRenderView.IRenderCallback
import com.jennifer.andy.simpleeyes.player.render.IRenderView.ISurfaceHolder
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.ISurfaceTextureHolder
import tv.danmaku.ijk.media.player.ISurfaceTextureHost
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 22:56
 * Description:
 */

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class TextureRenderView : TextureView, IRenderView {

    private lateinit var mMeasureHelper: MeasureHelper

    constructor(context: Context) : super(context) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initView(context)
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
        initView(context)
    }

    private fun initView(context: Context) {
        mMeasureHelper = MeasureHelper(this)
        mSurfaceCallback = SurfaceCallback(this)
        surfaceTextureListener = mSurfaceCallback
    }

    override fun getView(): View {
        return this
    }

    override fun shouldWaitForResize(): Boolean {
        return false
    }

    override fun onDetachedFromWindow() {
        mSurfaceCallback.willDetachFromWindow()
        super.onDetachedFromWindow()
        mSurfaceCallback.didDetachFromWindow()
    }

    //--------------------
    // Layout & Measure
    //--------------------
    override fun setVideoSize(videoWidth: Int, videoHeight: Int) {
        if (videoWidth > 0 && videoHeight > 0) {
            mMeasureHelper.setVideoSize(videoWidth, videoHeight)
            requestLayout()
        }
    }

    override fun setVideoSampleAspectRatio(videoSarNum: Int, videoSarDen: Int) {
        if (videoSarNum > 0 && videoSarDen > 0) {
            mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen)
            requestLayout()
        }
    }

    override fun setVideoRotation(degree: Int) {
        mMeasureHelper.setVideoRotation(degree)
        rotation = degree.toFloat()
    }

    override fun setAspectRatio(aspectRatio: Int) {
        mMeasureHelper.setAspectRatio(aspectRatio)
        requestLayout()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(mMeasureHelper.measuredWidth, mMeasureHelper.measuredHeight)
    }

    //--------------------
    // TextureViewHolder
    //--------------------
    fun getSurfaceHolder(): ISurfaceHolder {
        return InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback)
    }

    /**
     * 内部surfaceHolder
     */
    private class InternalSurfaceHolder(private val textureView: TextureRenderView,
                                        override val surfaceTexture: SurfaceTexture?,
                                        private val mSurfaceTextureHost: ISurfaceTextureHost) : ISurfaceHolder {
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        override fun bindToMediaPlayer(mp: IMediaPlayer?) {
            if (mp == null) return
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
                    mp is ISurfaceTextureHolder) {
                val textureHolder = mp as ISurfaceTextureHolder
                textureView.mSurfaceCallback.setOwnSurfaceTexture(false)
                val surfaceTexture = textureHolder.surfaceTexture
                if (surfaceTexture != null) {
                    textureView.surfaceTexture = surfaceTexture
                } else {
                    textureHolder.surfaceTexture = this.surfaceTexture
                    textureHolder.setSurfaceTextureHost(textureView.mSurfaceCallback)
                }
            } else {
                mp.setSurface(openSurface())
            }
        }

        override val renderView: IRenderView
            get() = textureView

        override val surfaceHolder: SurfaceHolder?
            get() = null

        override fun openSurface(): Surface? {
            return if (surfaceTexture == null) null else Surface(surfaceTexture)
        }

    }

    //-------------------------
    // SurfaceHolder.Callback
    //-------------------------
    override fun addRenderCallback(callback: IRenderCallback) {
        mSurfaceCallback.addRenderCallback(callback)
    }

    override fun removeRenderCallback(callback: IRenderCallback) {
        mSurfaceCallback.removeRenderCallback(callback)
    }

    private lateinit var mSurfaceCallback: SurfaceCallback

    /**
     * SurfaceTexture监听
     */
    private class SurfaceCallback(renderView: TextureRenderView) : SurfaceTextureListener, ISurfaceTextureHost {
        var mSurfaceTexture: SurfaceTexture? = null
        private var mIsFormatChanged = false
        private var mWidth = 0
        private var mHeight = 0
        private var mOwnSurfaceTexture = true
        private var mWillDetachFromWindow = false
        private var mDidDetachFromWindow = false
        private val mWeakRenderView: WeakReference<TextureRenderView> = WeakReference(renderView)
        private val mRenderCallbackMap: MutableMap<IRenderCallback, Any> = ConcurrentHashMap()

        fun setOwnSurfaceTexture(ownSurfaceTexture: Boolean) {
            mOwnSurfaceTexture = ownSurfaceTexture
        }

        /**
         * 添加渲染回调
         */
        fun addRenderCallback(callback: IRenderCallback) {
            mRenderCallbackMap[callback] = callback
            var surfaceHolder: ISurfaceHolder? = null
            if (mSurfaceTexture != null) {
                if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, mSurfaceTexture, this)
                callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight)
            }
            if (mIsFormatChanged) {
                if (surfaceHolder == null) surfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, mSurfaceTexture, this)
                callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight)
            }
        }

        fun removeRenderCallback(callback: IRenderCallback) {
            mRenderCallbackMap.remove(callback)
        }

        /**
         * 当纹理面板可用的时候
         */
        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
            mSurfaceTexture = surface
            mIsFormatChanged = false
            mWidth = 0
            mHeight = 0
            val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, surface, this)
            for (renderCallback in mRenderCallbackMap.keys) {
                renderCallback.onSurfaceCreated(surfaceHolder, 0, 0)
            }
        }

        /**
         * 当纹理面板大小发生改变的时候
         */
        override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
            mSurfaceTexture = surface
            mIsFormatChanged = true
            mWidth = width
            mHeight = height
            val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, surface, this)
            for (renderCallback in mRenderCallbackMap.keys) {
                renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height)
            }
        }

        /**
         * 当表面纹理摧毁的时候
         */
        override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
            mSurfaceTexture = surface
            mIsFormatChanged = false
            mWidth = 0
            mHeight = 0
            val surfaceHolder: ISurfaceHolder = InternalSurfaceHolder(mWeakRenderView.get()!!, surface, this)
            for (renderCallback in mRenderCallbackMap.keys) {
                renderCallback.onSurfaceDestroyed(surfaceHolder)
            }
            Log.d(TAG, "onSurfaceTextureDestroyed: destroy: $mOwnSurfaceTexture")
            return mOwnSurfaceTexture
        }

        /**
         * 当表面纹理更新的时候
         */
        override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}

        //-------------------------
        // ISurfaceTextureHost
        //-------------------------
        override fun releaseSurfaceTexture(surfaceTexture: SurfaceTexture) {
            if (surfaceTexture == null) {
                Log.d(TAG, "releaseSurfaceTexture: null")
            } else if (mDidDetachFromWindow) {
                if (surfaceTexture !== mSurfaceTexture) {
                    Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture")
                    surfaceTexture.release()
                } else if (!mOwnSurfaceTexture) {
                    Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture")
                    surfaceTexture.release()
                } else {
                    Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView")
                }
            } else if (mWillDetachFromWindow) {
                if (surfaceTexture !== mSurfaceTexture) {
                    Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture")
                    surfaceTexture.release()
                } else if (!mOwnSurfaceTexture) {
                    Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView")
                    setOwnSurfaceTexture(true)
                } else {
                    Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView")
                }
            } else {
                if (surfaceTexture !== mSurfaceTexture) {
                    Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture")
                    surfaceTexture.release()
                } else if (!mOwnSurfaceTexture) {
                    Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView")
                    setOwnSurfaceTexture(true)
                } else {
                    Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView")
                }
            }
        }

        /**
         * 将要从window消失
         */
        fun willDetachFromWindow() {
            Log.d(TAG, "willDetachFromWindow()")
            mWillDetachFromWindow = true
        }

        /**
         * 已经从window消失
         */
        fun didDetachFromWindow() {
            Log.d(TAG, "didDetachFromWindow()")
            mDidDetachFromWindow = true
        }

    }

    //--------------------
    // Accessibility
    //--------------------
    override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
        super.onInitializeAccessibilityEvent(event)
        event.className = TextureRenderView::class.java.name
    }

    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
        super.onInitializeAccessibilityNodeInfo(info)
        info.className = TextureRenderView::class.java.name
    }

    companion object {
        private const val TAG = "TextureRenderView"
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ControllerView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.MediaController.MediaPlayerControl
import com.jennifer.andy.simpleeyes.entity.ContentBean
import com.jennifer.andy.simpleeyes.player.IjkMediaController
import com.jennifer.andy.simpleeyes.player.event.VideoProgressEvent
import com.jennifer.andy.simpleeyes.utils.stringForTime
import java.util.*


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 23:00
 * Description:
 */

abstract class ControllerView(context: Context) : FrameLayout(context) {

    protected lateinit var mPlayer: MediaPlayerControl
    protected lateinit var mController: IjkMediaController
    protected var mCurrentVideoInfo: ContentBean? = null

    var isDragging = false//是否正在拖动
    private var mErrorView: ErrorView? = null
    private val mContentViews: MutableList<View> = ArrayList()

    abstract val layoutId: Int

    fun initControllerAndData(player: MediaPlayerControl, controller: IjkMediaController, currentVideoInfo: ContentBean?) {
        mPlayer = player
        mController = controller
        mCurrentVideoInfo = currentVideoInfo
        LayoutInflater.from(context).inflate(layoutId, this, true)
        initView()
        initData()
    }

    /**
     * 初始化布局
     */
    abstract fun initView()

    /**
     * 初始化数据
     */
    abstract fun initData()

    /**
     * 显示
     */
    fun display() {
        updateTogglePauseUI(mPlayer.isPlaying)
    }

    /**
     * 更新暂停切换UI
     *
     * @param isPlaying 是否播放
     */
    open fun updateTogglePauseUI(isPlaying: Boolean) {}

    /**
     * 更新当前视频播放进度
     *
     * @param progress          第一进度
     * @param secondaryProgress 第二进度
     */
    open fun updateProgress(progress: Int, secondaryProgress: Int) {}

    /**
     * 更新当前视频播放时间
     *
     * @param currentTime 当前时间
     * @param endTime     结束时间
     */
    open fun updateTime(currentTime: String, endTime: String) {}

    /**
     * 根据传入的[videoProgressEvent] 更新播放进度与时间
     *
     * @param videoProgressEvent 播放进度事件
     */
    fun updateProgressAndTime(videoProgressEvent: VideoProgressEvent) {
        if (!isDragging) { //没在拖动的时候跟新进度条
            updateProgress(videoProgressEvent.progress, videoProgressEvent.secondaryProgress)
            updateTime(stringForTime(videoProgressEvent.currentPosition), stringForTime(videoProgressEvent.duration))
        }
    }

    /**
     * 暂停切换
     */
    fun togglePause() {
        if (mPlayer.isPlaying) {
            mPlayer.pause()
        } else {
            mPlayer.start()
        }
        updateTogglePauseUI(mPlayer.isPlaying)
    }


    internal enum class State {
        ERROR
    }

    override fun addView(child: View?, params: ViewGroup.LayoutParams?) {
        super.addView(child, params)
        if (child != null && child.tag !== State.ERROR) mContentViews.add(child)
    }

    /**
     * 显示错误界面
     */
    fun showErrorView() {
        for (contentView in mContentViews) {
            contentView.visibility = View.GONE
        }
        if (mErrorView == null) {
            mErrorView = ErrorView(context).apply {
                setController(mController)
                tag = State.ERROR
            }
            addView(mErrorView)
        } else {
            mErrorView!!.visibility = View.VISIBLE
        }
    }

    /**
     * 显示内容界面,默认会将错误界面隐藏
     */
    fun showContent() {
        for (contentView in mContentViews) {
            contentView.visibility = View.VISIBLE
        }
        if (mErrorView != null) {
            mErrorView!!.visibility = View.GONE
        }
    }

}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ControllerViewFactory.kt
================================================
package com.jennifer.andy.simpleeyes.player.view

import android.content.Context


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 23:26
 * Description:
 */

class ControllerViewFactory {

    companion object {
        const val TINY_MODE = 0
        const val FULL_SCREEN_MODE = 1
    }

    fun create(showMode: Int, context: Context): ControllerView {
        return when (showMode) {
            TINY_MODE -> TinyControllerView(context)
            FULL_SCREEN_MODE -> FullScreenControllerView(context)
            else -> throw IllegalArgumentException("not correct showMode")
        }
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ErrorView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.player.IjkMediaController


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 9:19 PM
 * Description: 错误界面,用于[ControllerView]显示的网络错误界面。
 */
class ErrorView(context: Context) : FrameLayout(context), View.OnClickListener {

    private val mIvReload: ImageView
    private lateinit var mController: IjkMediaController

    fun setController(controller: IjkMediaController) {
        mController = controller
    }

    override fun onClick(v: View) {
        val rotation = ObjectAnimator.ofFloat(mIvReload, "rotation", 0f, 360f)
        rotation.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) { //执行选中动画,然后开始刷新操作
                mController.hide()
                mController.controllerListener?.onErrorViewClick()
            }
        })
        rotation.duration = 500
        rotation.start()
    }

    init {
        LayoutInflater.from(context).inflate(R.layout.layout_video_error, this, true)
        mIvReload = findViewById(R.id.iv_reload)
        setOnClickListener(this)
    }
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/FullScreenControllerView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view

import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.SeekBar
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.utils.stringForTime
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 23:17
 * Description:
 */

class FullScreenControllerView(context: Context) : ControllerView(context), View.OnClickListener {

    private lateinit var mMinScreen: ImageView
    private lateinit var mPreButton: ImageView
    private lateinit var mPauseButton: ImageView
    private lateinit var mNextButton: ImageView
    private lateinit var mProgress: SeekBar
    private lateinit var mTitle: CustomFontTextView
    private lateinit var mEndTime: CustomFontTextView
    private lateinit var mCurrentTime: CustomFontTextView

    private var mChangeProgress = 0

    companion object {
        const val FULL_SCREEN_ID = 100
    }

    override val layoutId = R.layout.layout_media_controller_full_screen

    override fun initView() {
        mMinScreen = findViewById(R.id.iv_min_screen)
        mProgress = findViewById(R.id.sb_progress)
        mTitle = findViewById(R.id.tv_title)
        mPreButton = findViewById(R.id.iv_previous)
        mPauseButton = findViewById(R.id.iv_pause)
        mNextButton = findViewById(R.id.iv_next)
        mProgress = findViewById(R.id.sb_progress)
        mCurrentTime = findViewById(R.id.tv_currentTime)
        mEndTime = findViewById(R.id.tv_end_time)
        initListener()
    }

    private fun initListener() {
        mPreButton.setOnClickListener(this)
        mMinScreen.setOnClickListener(this)
        mPauseButton.setOnClickListener(this)
        mNextButton.setOnClickListener(this)
        setOnClickListener(this)

        mProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onStartTrackingTouch(bar: SeekBar) { //控制的时候停止更新进度条,同时禁止隐藏
                isDragging = true
                mController.showAllTheTime()
            }

            override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) {
                if (fromUser) { //更新当前播放时间
                    val duration = mPlayer.duration.toLong()
                    val newPosition = duration * progress / 1000L
                    mChangeProgress = progress
                    mCurrentTime.text = stringForTime(newPosition.toInt())
                }
            }

            override fun onStopTrackingTouch(bar: SeekBar) { //定位都拖动位置
                val newPosition = mPlayer.duration * mChangeProgress / 1000L
                mPlayer.seekTo(newPosition.toInt())
                mController.show() //开启延时隐藏
                isDragging = false
            }
        })
    }

    override fun initData() {
        //初始化标题
        mTitle.text = mCurrentVideoInfo!!.title

        //初始化播放时间
        val position = mPlayer.currentPosition
        val duration = mPlayer.duration

        mCurrentTime.text = stringForTime(position)
        mEndTime.text = stringForTime(duration)

        //初始化进度条
        mProgress.apply {
            max = 1000
            if (duration >= 0 && mPlayer.bufferPercentage > 0) {
                val firstProgress = 1000L * position / duration
                val secondProgress = mPlayer.bufferPercentage * 10
                progress = firstProgress.toInt()
                secondaryProgress = secondProgress
            }
            setPadding(0, 0, 0, 0)
        }
        updatePreNextButton()
        id = FULL_SCREEN_ID
    }


    //判断是否显示上一个按钮与下一个按钮
    private fun updatePreNextButton() {
        mPreButton.visibility = if (mController.isHavePreVideo()) View.VISIBLE else View.GONE
        mNextButton.visibility = if (mController.isHaveNextVideo()) View.VISIBLE else View.GONE
    }


    override fun onClick(v: View) {
        when (v.id) {
            FULL_SCREEN_ID -> mController.hide()
            R.id.iv_pause -> {
                togglePause()
                mController.show()
            }
            R.id.iv_previous -> {
                mController.controllerListener?.onPreClick()
                updatePreNextButton()
            }
            R.id.iv_next -> {
                mController.controllerListener?.onNextClick()
                updatePreNextButton()
            }
            R.id.iv_min_screen -> {
                mController.switchControllerView(ControllerViewFactory.TINY_MODE)
                mController.controllerListener?.onTinyScreenClick()
            }
        }
    }

    override fun updateProgress(progress: Int, secondaryProgress: Int) {
        mProgress.progress = progress
        mProgress.secondaryProgress = secondaryProgress
    }

    override fun updateTime(currentTime: String, endTime: String) {
        mCurrentTime.text = currentTime
        mEndTime.text = endTime
    }

    override fun updateTogglePauseUI(isPlaying: Boolean) {
        if (isPlaying) {
            mPauseButton.setImageResource(R.drawable.ic_player_pause)
        } else {
            mPauseButton.setImageResource(R.drawable.ic_player_play)
        }
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/player/view/TinyControllerView.kt
================================================
package com.jennifer.andy.simpleeyes.player.view

import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.utils.stringForTime


/**
 * Author:  andy.xwt
 * Date:    2020/3/9 23:37
 * Description:
 */

class TinyControllerView(context: Context) : ControllerView(context), View.OnClickListener {

    private lateinit var mPauseButton: ImageView
    private lateinit var mPreButton: ImageView
    private lateinit var mBackButton: ImageView
    private lateinit var mNextButton: ImageView
    private lateinit var mFullScreen: ImageView

    private lateinit var mEndTime: TextView
    private lateinit var mCurrentTime: TextView

    override val layoutId = R.layout.layout_media_controller_tiny

    override fun initView() {
        mPauseButton = findViewById(R.id.iv_pause)
        mPreButton = findViewById(R.id.iv_previous)
        mBackButton = findViewById(R.id.iv_back)
        mNextButton = findViewById(R.id.iv_next)
        mFullScreen = findViewById(R.id.iv_full_screen)
        mEndTime = findViewById(R.id.tv_end_time)
        mCurrentTime = findViewById(R.id.tv_currentTime)
        initListener()
    }

    private fun initListener() {
        mPreButton.setOnClickListener(this)
        mPauseButton.setOnClickListener(this)
        mNextButton.setOnClickListener(this)
        mBackButton.setOnClickListener(this)
        mFullScreen.setOnClickListener(this)
    }

    override fun initData() { //初始化开始时间
        val position = mPlayer.currentPosition
        val duration = mPlayer.duration
        mCurrentTime.text = stringForTime(position)
        mEndTime.text = "/${stringForTime(duration)}"
        updatePreNextButton()
    }


    //判断是否显示上一个按钮与下一个按钮
    private fun updatePreNextButton() {
        mPreButton.visibility = if (mController.isHavePreVideo()) View.VISIBLE else View.GONE
        mNextButton.visibility = if (mController.isHaveNextVideo()) View.VISIBLE else View.GONE
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.iv_pause -> {
                togglePause()
                mController.show()
            }
            R.id.iv_previous -> {
                mController.controllerListener?.onPreClick()
                updatePreNextButton()
            }
            R.id.iv_next -> {
                mController.controllerListener?.onNextClick()
                updatePreNextButton()
            }
            R.id.iv_back -> {
                mController.hide()
                mController.controllerListener?.onBackClick()
            }
            R.id.iv_full_screen -> {
                mController.switchControllerView(ControllerViewFactory.FULL_SCREEN_MODE)
                mController.controllerListener?.onFullScreenClick()
            }
        }
    }

    override fun updateProgress(progress: Int, secondaryProgress: Int) {}


    override fun updateTime(currentTime: String, endTime: String) {
        mCurrentTime.text = currentTime
        mEndTime.text = "/$endTime"
    }

    override fun updateTogglePauseUI(isPlaying: Boolean) {
        if (isPlaying) {
            mPauseButton.setImageResource(R.drawable.ic_player_pause)
        } else {
            mPauseButton.setImageResource(R.drawable.ic_player_play)
        }
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/router/EyesPathReplaceService.kt
================================================
package com.jennifer.andy.simpleeyes.router

import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.facade.service.PathReplaceService
import java.util.regex.Pattern


/**
 * Author:  andy.xwt
 * Date:    2018/7/20 14:08
 * Description:
 * 这里发现开眼的ActionUrl不是正常的scheme格式,因为当我们通过Uri跳转的时候会出现问题。所以这里要对其进行替换操作
 */
@Route(path = "/github/AndyJennifer")
class EyesPathReplaceService : PathReplaceService {


    companion object {
        const val HOST = "eyepetizer://github.com/"
        const val SCHEME = "eyepetizer"
        const val ONE_LEVEL = "AndyJennifer/"

    }

    override fun forString(path: String): String {
        return path
    }

    override fun forUri(uri: Uri): Uri {

        if (uri.scheme == SCHEME) {
            val uriStr = uri.toString()
            val split = uriStr.split("eyepetizer://")
            when {
                uriStr.contains("pgc/detail") -> //处理作者详情
                    return setPageDetailUrl(split)
                uriStr.contains("webview") -> //处理webview
                    return setWebViewUrl(split)
                uriStr.contains("detail") -> //处理webview中点击跳转
                    return setWebVideoUrl(split)
                uriStr.contains("ranklist") ->//处理排行榜
                    return setRankListUrl(split)
                uriStr.contains("campaign") ->//处理专题
                    return setTopicUrl(split)
                uriStr.contains("tag") ->//处理360全景
                    return setTagUrl(split)
                uriStr.contains("category") ->//处理分类
                    return setCategoryUrl(split)
                uriStr.contains("common") ->//处理公共RecyclerView界面
                    return setCommonUrl(split)
                else -> {
                }
            }


        }
        return uri
    }


    /**
     * 处理作者详情url
     * eyepetizer://pgc/detail/1065/?title=DELISH%20KITCHEN%20%E5%8F%AF%E5%8F%A3%E5%8E%A8%E6%88%BF&userType=PGC&tabIndex=1
     * 替换为:
     * eyepetizer://github.com/pgc/detail/?title=DELISH%20KITCHEN%20%E5%8F%AF%E5%8F%A3%E5%8E%A8%E6%88%BF&userType=PGC&tabIndex=1?id=1065
     */
    private fun setPageDetailUrl(split: List<String>): Uri {
        //获取id
        val pt = Pattern.compile("/\\d+/")
        val matcher = pt.matcher(split[1])
        var id = 0
        if (matcher.find()) {
            id = matcher.group().replace("/", "").toInt()
        }
        //替换/1065/为空字符串
        val uriWithoutNumber = split[1].replace(Regex("/\\d+/"), "")
        return "$HOST$uriWithoutNumber&id=$id".toUri()
    }

    /**
     * 处理webViewUrl
     * eyepetizer://webview/?title=%E5%B9%BF%E5%9C%BA&url=http%3A%2F%2Fwww.kaiyanapp.com%2Fcampaign%2Ftag_square%2Ftag_square.html%3Fnid%3D1207%26tid%3D845%26cookie%3D%26udid%3Dd0f6190461864a3a978bdbcb3fe9b48709f1f390%26shareable%3Dtrue
     * 替换为:eyepetizer://github.com/AndyJennifer/webview/?title=%E5%B9%BF%E5%9C%BA&url=http%3A%2F%2Fwww.kaiyanapp.com%2Fcampaign%2Ftag_square%2Ftag_square.html%3Fnid%3D1207%26tid%3D845%26cookie%3D%26udid%3Dd0f6190461864a3a978bdbcb3fe9b48709f1f390%26shareable%3Dtrue
     */
    private fun setWebViewUrl(split: List<String>): Uri {
        return Uri.parse("$HOST$ONE_LEVEL${split[1]}")
    }

    /**
     * 处理webView点击跳转视频信息
     * eyepetizer://detail/114760
     * 替换为:eyepetizer://github.com/AndyJennifer/detail?id =114760
     */
    private fun setWebVideoUrl(split: List<String>): Uri {
        //获取id
        val pt = Pattern.compile("/\\d+")
        val matcher = pt.matcher(split[1])
        var id = 0
        if (matcher.find()) {
            id = matcher.group().replace("/", "").toInt()
        }
        //替换/1065/为空字符串
        val uriWithoutNumber = split[1].replace(Regex("/\\d+"), "")
        return "$HOST$ONE_LEVEL$uriWithoutNumber?id=$id".toUri()
    }

    /**
     * 处理排行榜
     * eyepetizer://ranklist/
     * 替换为:eyepetizer://github.com/AndyJennifer/ranklist
     */
    private fun setRankListUrl(split: List<String>): Uri {
        val str = split[1].replace("/", "")
        return "$HOST$ONE_LEVEL$str".toUri()
    }

    /**
     * 处理热门专题,
     * eyepetizer://campaign/list/?title=%E4%B8%93%E9%A2%98
     * 替换为:eyepetizer://github.com/campaign/list?title=%E4%B8%93%E9%A2%98
     */
    private fun setTopicUrl(split: List<String>): Uri {
        val str = split[1].replace("/?", "?")
        return "$HOST$str".toUri()
    }

    /**
     * 处理360全景
     * eyepetizer://tag/658/?title=360%C2%B0%E5%85%A8%E6%99%AF
     * 替换为:eyepetizer://github.com/AndyJennifer/tag?id=658&title=360%C2%B0%E5%85%A8%E6%99%AF
     */
    private fun setTagUrl(split: List<String>): Uri {
        //获取id
        val pt = Pattern.compile("/\\d+/")
        val matcher = pt.matcher(split[1])
        var id = 0
        if (matcher.find()) {
            id = matcher.group().replace("/", "").toInt()
        }
        //替换/658/为空字符串
        val uriWithoutNumber = split[1].replace(Regex("/\\d+/"), "")
        return "$HOST$ONE_LEVEL$uriWithoutNumber&id=$id".toUri()
    }

    /**
     * 处理360全景下面的所有的种类
     * eyepetizer://category/14/?title=%E5%B9%BF%E5%91%8A
     * 替换为:eyepetizer://github.com/AndyJennifer/category?id=14%title=%E5%B9%BF%E5%91%8A
     */
    private fun setCategoryUrl(split: List<String>): Uri {
        val pt = Pattern.compile("/\\d+/")
        val matcher = pt.matcher(split[1])
        var id = 0
        if (matcher.find()) {
            id = matcher.group().replace("/", "").toInt()
        }
        val uriWithoutNumber = split[1].replace(Regex("/\\d+/"), "")
        return "$HOST$ONE_LEVEL$uriWithoutNumber&id=$id".toUri()
    }

    /**
     * 处理公共界面中包含RecyclerVie的
     * eyepetizer://common/?title=VLOG&url=http%3A%2F%2Fbaobab.kaiyanapp.com%2Fapi%2Fv4%2Fplaylists%2F3946%2Fvideos
     * 替换为:eyepetizer://github.com/AndyJennifer/common?title=VLOG&url=http%3A%2F%2Fbaobab.kaiyanapp.com%2Fapi%2Fv4%2Fplaylists%2F3946%2Fvideos
     */
    private fun setCommonUrl(split: List<String>): Uri {
        val str = split[1].replace("/?", "?")
        return "$HOST$ONE_LEVEL$str".toUri()
    }

    override fun init(context: Context?) {

    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/router/RouteIntercept.kt
================================================
package com.jennifer.andy.simpleeyes.router

import android.content.Context
import com.alibaba.android.arouter.facade.Postcard
import com.alibaba.android.arouter.facade.annotation.Interceptor
import com.alibaba.android.arouter.facade.callback.InterceptorCallback
import com.alibaba.android.arouter.facade.template.IInterceptor
import com.alibaba.android.arouter.launcher.ARouter
import com.jennifer.andy.simpleeyes.UserPreferences


/**
 * Author:  andy.xwt
 * Date:    2019/1/14 19:04
 * Description: 1.登录拦截,判断用户是否登录
 */
@Interceptor(priority = 8, name = "登录拦截器")
class RouteIntercept : IInterceptor {

    companion object {
        const val SHOULD_LOGIN = 1 ushr 30
    }


    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        when (postcard.extra) {
            SHOULD_LOGIN -> judgeUserIsLogin(postcard, callback)
            else -> {
                callback.onContinue(postcard)
            }
        }
    }

    /**
     * 判断用户是否登录,如果没有登录则显示登录界面
     */
    private fun judgeUserIsLogin(postcard: Postcard, callback: InterceptorCallback) {
        val userIsLogin = UserPreferences.getUserIsLogin()
        if (!userIsLogin) //如果用户没登录,直接跳转到登录界面
            ARouter.getInstance().build("/github/Login").navigation()
        else
            callback.onContinue(postcard)

    }

    override fun init(context: Context?) {

    }


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/router/SchemeFilterActivity.kt
================================================
package com.jennifer.andy.simpleeyes.router

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.android.arouter.facade.Postcard
import com.alibaba.android.arouter.facade.callback.NavCallback
import com.alibaba.android.arouter.launcher.ARouter


/**
 * Author:  andy.xwt
 * Date:    2018/7/19 19:57
 * Description: 中间跳转页
 */
class SchemeFilterActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val uri = intent.data
        ARouter.getInstance().build(uri).navigation(this, object : NavCallback() {
            override fun onArrival(postcard: Postcard?) {
                finish()
            }
        })
    }
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/RxBus.kt
================================================
package com.jennifer.andy.simpleeyes.rx

import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import java.util.concurrent.ConcurrentHashMap


/**
 * Author:  andy.xwt
 * Date:    2018/2/12 14:06
 * Description: 参考地址:https://mcxiaoke.gitbooks.io/rxdocs/content/Subject.html
 */

object RxBus {

    private val mBus = PublishSubject.create<Any>().toSerialized()
    private val mSubjects = ConcurrentHashMap<String, CompositeDisposable>()

    /**
     * 发送一个事件
     */
    @JvmStatic
    fun post(o: Any) {
        mBus.onNext(o)
    }

    /**
     * 根据传递的 eventType 类型返回特定类型的(eventType)的 被观察者
     */
    private fun <T> toObservable(eventType: Class<T>): Observable<T> {
        return mBus.ofType(eventType)
    }


    /**
     * 订阅
     *
     * @param eventType 接受的事件类型
     * @param action    处理方法
     * @param error  错误处理方法
     * @param <T>    接受事件的泛型
     * @return
     */
    private fun <T> doSubscribe(eventType: Class<T>, action: Consumer<T>, error: Consumer<Throwable>): Disposable {
        return toObservable(eventType)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(action, error)
    }

    /**
     * 保存订阅后的disposable
     */
    private fun addDisposable(any: Any, disposable: Disposable) {
        val key = any.javaClass.name
        if (mSubjects[key] != null) {
            mSubjects[key]?.add(disposable)
        } else {
            val disposables = CompositeDisposable()
            disposables.add(disposable)
            mSubjects[key] = disposables
        }
    }

    /**
     * 处理注册事件
     *
     * @param event     事件
     * @param action    处理方式
     * @param subscribe 订阅者
     * @param <T>       事件类型泛型
     */
    @JvmStatic
    fun <T> register(subscribe: Any, event: Class<T>, action: Consumer<T>) {
        val disposable = doSubscribe(event, action, Consumer { throwable -> throw RuntimeException(throwable.message) })
        addDisposable(subscribe, disposable)
    }


    /**
     * 注销监听事件
     *
     * @param subscribe 订阅者
     */
    @JvmStatic
    fun unRegister(subscribe: Any) {
        val key = subscribe.javaClass.name
        if (mSubjects.containsKey(key) && mSubjects[key] != null) {
            mSubjects[key]?.dispose()
        }
        mSubjects.remove(key)
    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/RxThreadHelper.kt
================================================
package com.jennifer.andy.simpleeyes.rx

import io.reactivex.FlowableTransformer
import io.reactivex.MaybeTransformer
import io.reactivex.ObservableTransformer
import io.reactivex.SingleTransformer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers


/**
 * Author:  andy.xwt
 * Date:    2017/10/24 10:42
 * Description:网络请求帮助类处理。默认将观察者的方法切换到主线程中运行
 */

object RxThreadHelper {


    /**
     * 将Observable类型的观察者切换到主线程中运行
     */
    fun <T> switchObservableThread(): ObservableTransformer<T, T> {
        return ObservableTransformer {
            it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }

    /**
     * 将Flowable类型的观察者切换到主线程中运行
     */
    fun <T> switchFlowableThread(): FlowableTransformer<T, T> {
        return FlowableTransformer {
            it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }

    /**
     * 将Single类型的观察者切换到主线程中运行
     */
    fun <T> switchSingleThread(): SingleTransformer<T, T> {
        return SingleTransformer {
            it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }


    /**
     * 将Maybe类型的观察者切换到主线程中运行
     */
    fun <T> switchMaybeThread(): MaybeTransformer<T, T> {
        return MaybeTransformer {
            it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/FlowableRetryDelay.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error

import io.reactivex.Flowable
import io.reactivex.functions.Function
import org.reactivestreams.Publisher
import java.util.concurrent.TimeUnit


/**
 * Author:  andy.xwt
 * Date:    2019-11-17 21:24
 * Description:该类逻辑与[ObservableRetryDelay]一样
 */

class FlowableRetryDelay(
        val retryConfigProvider: (Throwable) -> RetryConfig
) : Function<Flowable<Throwable>, Publisher<*>> {

    private var retryCount: Int = 0

    override fun apply(throwableFlowable: Flowable<Throwable>): Publisher<*> {
        return throwableFlowable
                .flatMap { error ->
                    val (maxRetries, delay, retryTransform) = retryConfigProvider(error)

                    if (++retryCount <= maxRetries) {
                        retryTransform()
                                .flatMapPublisher { retry ->
                                    if (retry)
                                        Flowable.timer(delay.toLong(), TimeUnit.MILLISECONDS)
                                    else
                                        Flowable.error<Any>(error)
                                }
                    } else Flowable.error<Any>(error)
                }
    }
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/GlobalErrorProcessor.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error

import com.jennifer.andy.simpleeyes.utils.toast
import retrofit2.HttpException


/**
 * Author:  andy.xwt
 * Date:    2019-11-17 22:04
 * Description:
 */

fun <T> globalHandleError(): GlobalErrorTransformer<T> = GlobalErrorTransformer(
        globalDoOnErrorConsumer = { error ->
            when (error) {
                is HttpException -> {
                    when (error.code()) {
                        401 -> toast { "401 Unauthorized" }
                        404 -> toast { "404 failure" }
                        500 -> toast { "500 server failure" }
                        else -> toast { "network failure" }
                    }
                }
                else -> {
                    toast { error.message.toString() }
                }
            }
        }
)

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/GlobalErrorTransformer.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error

import io.reactivex.*


/**
 * Author:  andy.xwt
 * Date:    2019-11-17 21:22
 * Description: RxJava全局错误处理,包括:
 * 1.全局的onNext拦截器[globalOnNextInterceptor]
 * 2.全局的错误重试处理器[globalOnErrorResume],该属性用于当发生错误时,重新发送新的Observable
 * 3.全局重试配置参数[RetryConfig],该属性用于配置重试次数以及重试的时间,是否需要重试,需要通过内部的condition进行判断
 * 4.全局的错误处理器[globalDoOnErrorConsumer],该属性会在观察者的onError方法之前运行
 */

class GlobalErrorTransformer<T> constructor(
        private val globalOnNextInterceptor: (T) -> Observable<T> = { Observable.just(it) },
        private val globalOnErrorResume: (Throwable) -> Observable<T> = { Observable.error(it) },
        private val retryConfigProvider: (Throwable) -> RetryConfig = { RetryConfig() },
        private val globalDoOnErrorConsumer: (Throwable) -> Unit = { }
) : ObservableTransformer<T, T>,
        FlowableTransformer<T, T>,
        SingleTransformer<T, T>,
        MaybeTransformer<T, T>,
        CompletableTransformer {

    override fun apply(upstream: Observable<T>): Observable<T> =
            upstream
                    .flatMap {
                        globalOnNextInterceptor(it)
                    }
                    .onErrorResumeNext { throwable: Throwable ->
                        globalOnErrorResume(throwable)
                    }
                    .retryWhen(ObservableRetryDelay(retryConfigProvider))
                    .doOnError(globalDoOnErrorConsumer)

    override fun apply(upstream: Completable): Completable =
            upstream
                    .onErrorResumeNext {
                        globalOnErrorResume(it).ignoreElements()
                    }
                    .retryWhen(FlowableRetryDelay(retryConfigProvider))
                    .doOnError(globalDoOnErrorConsumer)

    override fun apply(upstream: Flowable<T>): Flowable<T> =
            upstream
                    .flatMap {
                        globalOnNextInterceptor(it)
                                .toFlowable(BackpressureStrategy.BUFFER)
                    }
                    .onErrorResumeNext { throwable: Throwable ->
                        globalOnErrorResume(throwable)
                                .toFlowable(BackpressureStrategy.BUFFER)
                    }
                    .retryWhen(FlowableRetryDelay(retryConfigProvider))
                    .doOnError(globalDoOnErrorConsumer)

    override fun apply(upstream: Maybe<T>): Maybe<T> =
            upstream
                    .flatMap {
                        globalOnNextInterceptor(it)
                                .firstElement()
                    }
                    .onErrorResumeNext { throwable: Throwable ->
                        globalOnErrorResume(throwable)
                                .firstElement()
                    }
                    .retryWhen(FlowableRetryDelay(retryConfigProvider))
                    .doOnError(globalDoOnErrorConsumer)

    override fun apply(upstream: Single<T>): Single<T> =
            upstream
                    .flatMap {
                        globalOnNextInterceptor(it)
                                .firstOrError()
                    }
                    .onErrorResumeNext { throwable ->
                        globalOnErrorResume(throwable)
                                .firstOrError()
                    }
                    .retryWhen(FlowableRetryDelay(retryConfigProvider))
                    .doOnError(globalDoOnErrorConsumer)

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/ObservabeRetryDelay.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error

import io.reactivex.Observable
import io.reactivex.ObservableSource
import io.reactivex.functions.Function
import java.util.concurrent.TimeUnit


/**
 * Author:  andy.xwt
 * Date:    2019-11-17 21:27
 * Description:该类会获取配置的重试参数。判断当前重试的次数是否大于配置的重试次数,如果比配置的小,那么会判断重试
 * 的条件,如果为true,那么就会重试,反之直接发送Error信息
 */

class ObservableRetryDelay(
        val retryConfigProvider: (Throwable) -> RetryConfig
) : Function<Observable<Throwable>, ObservableSource<*>> {

    private var retryCount: Int = 0

    override fun apply(throwableObs: Observable<Throwable>): ObservableSource<*> {
        return throwableObs
                .flatMap { error ->
                    val (maxRetries, delay, retryCondition) = retryConfigProvider(error)

                    if (++retryCount <= maxRetries) {
                        retryCondition().flatMapObservable { retry ->
                            if (retry)
                                Observable.timer(delay.toLong(), TimeUnit.MILLISECONDS)
                            else
                                Observable.error<Any>(error)
                        }
                    } else Observable.error<Any>(error)
                }
    }
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/RetryConfig.kt
================================================
package com.jennifer.andy.simpleeyes.rx.error

import io.reactivex.Single


/**
 * Author:  andy.xwt
 * Date:    2019-11-17 21:12
 * Description: 重试配置,默认情况下不会进行重试,如果需要重试配置,需要重写condition
 */

private const val DEFAULT_RETRY_TIMES = 1 //默认重试次数 1次
private const val DEFAULT_DELAY_DURATION = 1000 //默认推迟时间为1秒

data class RetryConfig(val maxRetries: Int = DEFAULT_RETRY_TIMES,
                       val delay: Int = DEFAULT_DELAY_DURATION,
                       val condition: () -> Single<Boolean> = { Single.just(false) })


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/MainActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui

import android.os.Bundle
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.ui.base.BaseAppCompatActivity
import com.jennifer.andy.simpleeyes.ui.feed.FeedFragment
import com.jennifer.andy.simpleeyes.ui.follow.FollowFragment
import com.jennifer.andy.simpleeyes.ui.home.HomeFragment
import com.jennifer.andy.simpleeyes.ui.profile.ProfileFragment
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.widget.BottomBar
import com.jennifer.andy.simpleeyes.widget.BottomItem
import me.yokeyword.fragmentation.SupportFragment


/**
 * Author:  andy.xwt
 * Date:    2017/9/19 18:54
 * Description:主界面
 */

class MainActivity : BaseAppCompatActivity() {


    private var mFragments = arrayOfNulls<SupportFragment>(4)
    private val mBottomNavigation: BottomBar by bindView(R.id.bottom_navigation_bar)

    companion object {
        private const val FIRST = 0
        private const val SECOND = 1
        private const val THIRD = 2
        private const val FOURTH = 3
    }


    override fun initView(savedInstanceState: Bundle?) {
        mFragments[FIRST] = HomeFragment.newInstance()
        mFragments[SECOND] = FeedFragment.newInstance()
        mFragments[THIRD] = FollowFragment.newInstance()
        mFragments[FOURTH] = ProfileFragment.newInstance()
        loadMultipleRootFragment(R.id.fl_container, FIRST, *mFragments)
        initBottomNavigation()
    }

    private fun initBottomNavigation() {

        val home = BottomItem(R.drawable.ic_tab_strip_icon_feed_selected, getString(R.string.home))
        home.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_feed)
        val discover = BottomItem(R.drawable.ic_tab_strip_icon_category_selected, getString(R.string.discover))
        discover.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_category)
        val focus = BottomItem(R.drawable.ic_tab_strip_icon_follow_selected, getString(R.string.focus))
        focus.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_follow)
        val mine = BottomItem(R.drawable.ic_tab_strip_icon_profile_selected, getString(R.string.mine))
        mine.setUnSelectedDrawable(R.drawable.ic_tab_strip_icon_profile)

        with(mBottomNavigation) {
            addItem(home)
            addItem(discover)
            addItem(focus)
            addItem(mine)
            initialise()
            setOnTabSelectedListener(object : BottomBar.TabSelectedListener {
                override fun onTabSelected(position: Int, prePosition: Int) {
                    showHideFragment(mFragments[position])
                }

                override fun onTabUnselected(position: Int) {


                }

                override fun onTabReselected(position: Int) {
                    if (position == FIRST) {
                        val categoryFragment = mFragments[FIRST] as HomeFragment
                        categoryFragment.scrollToTop()
                    }
                }
            })

        }

    }

    override fun getContentViewLayoutId() = R.layout.activity_main


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/AuthorTagDetailActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author

import android.animation.ArgbEvaluator
import android.graphics.Color
import android.os.Bundle
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.entity.TabInfo
import com.jennifer.andy.simpleeyes.net.Extras
import com.jennifer.andy.simpleeyes.ui.author.presenter.AuthorTagDetailPresenter
import com.jennifer.andy.simpleeyes.ui.author.ui.AuthorTagDetailView
import com.jennifer.andy.simpleeyes.ui.base.BaseActivity
import com.jennifer.andy.simpleeyes.ui.base.BaseFragmentItemAdapter
import com.jennifer.andy.simpleeyes.ui.feed.TagDetailInfoFragment
import com.jennifer.andy.simpleeyes.utils.bindView
import com.jennifer.andy.simpleeyes.utils.showKeyboard
import com.jennifer.andy.simpleeyes.widget.StickyNavLayout
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.tab.ShortTabLayout


/**
 * Author:  andy.xwt
 * Date:    2019-07-13 22:38
 * Description: 作者详细信息界面
 */

@Route(path = "/pgc/detail")
class AuthorTagDetailActivity : BaseActivity<AuthorTagDetailView, AuthorTagDetailPresenter>(), AuthorTagDetailView {

    private val mToolbar: RelativeLayout by bindView(R.id.tool_bar)
    private val mTvTitle: CustomFontTextView by bindView(R.id.tv_title)
    private val mStickyNavLayout: StickyNavLayout by bindView(R.id.stick_layout)
    private val mViewPager: ViewPager by bindView(R.id.id_sticky_nav_layout_viewpager)
    private val mTabLayout: ShortTabLayout by bindView(R.id.id_sticky_nav_layout_nav_view)

    private val mTvName: CustomFontTextView by bindView(R.id.tv_name)
    private val mTvDesc: CustomFontTextView by bindView(R.id.tv_desc)
    private val mTvBrief: CustomFontTextView by bindView(R.id.tv_brief)
    private val mIvHead: SimpleDraweeView by bindView(R.id.iv_head)

    @Autowired
    @JvmField
    var tabIndex: String? = null

    @Autowired
    @JvmField
    var title: String? = null

    @Autowired
    @JvmField
    var id: String? = null


    override fun getBundleExtras(extras: Bundle) {
        with(extras) {
            tabIndex = getString(Extras.TAB_INDEX)
            title = getString(Extras.TITLE)
            id = getString(Extras.ID)
        }
    }

    override fun initView(savedInstanceState: Bundle?) {
        ARouter.getInstance().inject(this)
        initToolBar(title, 0f)
        mPresenter.getAuthorTagDetail(id!!)

    }


    override fun loadInfoSuccess(tab: Tab) {

        mViewPager.adapter = BaseFragmentItemAdapter(supportFragmentManager, initFragments(tab.tabInfo), initTitles(tab.tabInfo))
        mTabLayout.setupWithViewPager(mViewPager)
        mViewPager.currentItem = tabIndex?.toInt() ?: 0

        mStickyNavLayout.setScrollChangeListener(object : StickyNavLayout.ScrollChangeListener {
            override fun onScroll(moveRatio: Float) {
                initToolBar(title, moveRatio)
            }
        })

        //设置topView信息
        with(tab.pgcInfo) {
            mTvName.text = name
            mTvDesc.text = description
            mTvBrief.text = brief
            mIvHead.setImageURI(icon)
        }


    }

    private fun initFragments(tabInfo: TabInfo): MutableList<Fragment> {
        val fragments = mutableListOf<Fragment>()
        for (i in tabInfo.tabList.indices) {
            fragments.add(TagDetailInfoFragment.newInstance(tabInfo.tabList[i].apiUrl))
        }
        return fragments
    }

    private fun initTitles(tabInfo: TabInfo): MutableList<String> {
        val titles = mutableListOf<String>()
        for (i in tabInfo.tabList.indices) {
            titles.add(tabInfo.tabList[i].name)
        }
        return titles
    }

    private fun initToolBar(title: String? = null, titleAlpha: Float = 1f) {
        findViewById<ImageView>(R.id.iv_back).setOnClickListener {
            showKeyboard(false)
            finish()
        }
        //设置背景渐变
        val color = ArgbEvaluator().evaluate(titleAlpha, 0x00FFFFFF, Color.WHITE) as Int
        mToolbar.setBackgroundColor(color)

        mTvTitle.apply {
            setFontType(fontType = FontType.BOLD)
            text = title
            alpha = titleAlpha
        }

    }

    override fun getContentViewLayoutId() = R.layout.activity_author_tag_detail
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/model/AuthorModel.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author.model

import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.net.Api
import com.jennifer.andy.simpleeyes.rx.RxThreadHelper
import com.jennifer.andy.simpleeyes.rx.error.globalHandleError
import com.jennifer.andy.simpleeyes.ui.base.model.BaseModel
import io.reactivex.Observable


/**
 * Author:  andy.xwt
 * Date:    2019-07-13 22:34
 * Description:
 */

class AuthorModel : BaseModel {

    /**
     * 获取作者
     */
    fun getAuthorTagDetail(id: String): Observable<Tab> =
            Api.getDefault()
                    .getAuthorTagDetail(id)
                    .compose(globalHandleError())
                    .compose(RxThreadHelper.switchObservableThread())

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/presenter/AuthorTagDetailPresenter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author.presenter

import android.view.View
import com.jennifer.andy.simpleeyes.ui.author.model.AuthorModel
import com.jennifer.andy.simpleeyes.ui.author.ui.AuthorTagDetailView
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.uber.autodispose.autoDispose


/**
 * Author:  andy.xwt
 * Date:    2019-07-13 22:40
 * Description:
 */

class AuthorTagDetailPresenter : BasePresenter<AuthorTagDetailView>() {

    private var mAuthorModel: AuthorModel = AuthorModel()

    fun getAuthorTagDetail(id: String) {
        mAuthorModel.getAuthorTagDetail(id).autoDispose(mScopeProvider).subscribe({
            mView?.showContent()
            mView?.loadInfoSuccess(it)
        }, {
            mView?.showNetError(View.OnClickListener { getAuthorTagDetail(id) })
        })
    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/ui/AuthorTagDetailView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.author.ui

import com.jennifer.andy.simpleeyes.entity.Tab
import com.jennifer.andy.simpleeyes.ui.base.BaseView


/**
 * Author:  andy.xwt
 * Date:    2019-07-13 22:39
 * Description:
 */

interface AuthorTagDetailView : BaseView {
    fun loadInfoSuccess(tab: Tab)
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base

import android.os.Bundle
import android.view.View
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.utils.getGenericInstance


/**
 * Author:  andy.xwt
 * Date:    2017/9/5 19:04
 * Description: 基础类activity
 */

@Suppress("UNCHECKED_CAST")
abstract class BaseActivity<V, T : BasePresenter<V>> : BaseAppCompatActivity(), BaseView {


    protected lateinit var mPresenter: T


    override fun onCreate(savedInstanceState: Bundle?) {
        mPresenter = getGenericInstance(this, 1)
        lifecycle.addObserver(mPresenter)
        mPresenter.attachView(this as V, this)
        super.onCreate(savedInstanceState)
    }

    override fun showLoading() {
        mMultipleStateView.showLoading()
    }

    override fun showNetError(onClickListener: View.OnClickListener) {
        mMultipleStateView.showNetError(onClickListener)
    }

    override fun showEmpty(onClickListener: View.OnClickListener) {
        mMultipleStateView.showEmpty(onClickListener)
    }

    override fun showContent() {
        mMultipleStateView.showContent()
    }

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseAppCompatActivity.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base

import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.StringRes
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.manager.ActivityManager
import com.jennifer.andy.simpleeyes.utils.showKeyboard
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
import me.yokeyword.fragmentation.SupportActivity


/**
 * Author:  andy.xwt
 * Date:    2017/8/10 22:37
 * Description:
 */

abstract class BaseAppCompatActivity : SupportActivity() {

    /**
     * 上下文对象
     */
    protected lateinit var mContext: Context
    protected lateinit var mMultipleStateView: MultipleStateView


    /**
     * 跳转到其他Activity启动或者退出的模式
     */
    enum class TransitionMode {
        LEFT, RIGHT, TOP, BOTTOM, SCALE, FADE
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initData()
        initView(savedInstanceState)
    }

    override fun onStart() {
        super.onStart()
        ActivityManager.getInstance().addActivity(this)
    }

    private fun initData() {
        overrideTransitionAnimation()
        val extras = intent.extras
        if (extras != null) {
            getBundleExtras(extras)
        }
        mContext = this
        //添加相应的布局
        if (getContentViewLayoutId() != 0) {
            mMultipleStateView = MultipleStateView(this)
            val view = View.inflate(this, getContentViewLayoutId(), mMultipleStateView)
            setContentView(view)
        } else {
            throw  IllegalArgumentException("You must return layout id")
        }

    }

    /**
     * 设置activity进入动画
     */
    private fun overrideTransitionAnimation() {
        if (toggleOverridePendingTransition()) {
            when (getOverridePendingTransition()) {
                TransitionMode.TOP -> overridePendingTransition(R.anim.top_in, R.anim.no_anim)
                TransitionMode.BOTTOM -> overridePendingTransition(R.anim.bottom_in, R.anim.no_anim)
                TransitionMode.LEFT -> overridePendingTransition(R.anim.left_in, R.anim.no_anim)
                TransitionMode.RIGHT -> overridePendingTransition(R.anim.right_in, R.anim.no_anim)
                TransitionMode.FADE -> overridePendingTransition(R.anim.fade_in, R.anim.no_anim)
                TransitionMode.SCALE -> overridePendingTransition(R.anim.scale_in, R.anim.no_anim)
            }
        }
    }

    override fun finish() {
        super.finish()
        if (toggleOverridePendingTransition()) {
            when (getOverridePendingTransition()) {
                TransitionMode.TOP -> overridePendingTransition(0, R.anim.top_out)
                TransitionMode.BOTTOM -> overridePendingTransition(0, R.anim.bottom_out)
                TransitionMode.LEFT -> overridePendingTransition(0, R.anim.left_out)
                TransitionMode.RIGHT -> overridePendingTransition(0, R.anim.right_out)
                TransitionMode.FADE -> overridePendingTransition(0, R.anim.fade_out)
                TransitionMode.SCALE -> overridePendingTransition(0, R.anim.scale_out)
            }
        }

    }

    override fun onStop() {
        super.onStop()
        ActivityManager.getInstance().removeActivity(this)
    }


    /**
     * 初始化工具栏,默认情况下加粗
     */
    protected fun initToolBar(toolbar: ViewGroup, title: String? = null, fontType: FontType = FontType.BOLD) {
        val ivBack = toolbar.findViewById<ImageView>(R.id.iv_back)
        ivBack.setOnClickListener {
            showKeyboard(false)
            finish()
        }

        val tvTitle = toolbar.findViewById<CustomFontTextView>(R.id.tv_title)
        tvTitle.setFontType(fontType)
        tvTitle.text = title

    }

    /**
     * 初始化工具栏,默认情况下加粗
     */
    protected fun initToolBar(toolbar: ViewGroup, @StringRes id: Int? = null, fontType: FontType = FontType.BOLD) {
        val ivBack = toolbar.findViewById<ImageView>(R.id.iv_back)
        ivBack.setOnClickListener {
            showKeyboard(false)
            finish()
        }

        val tvTitle = toolbar.findViewById<CustomFontTextView>(R.id.tv_title)
        tvTitle.setFontType(fontType)
        tvTitle.setText(id!!)

    }


    abstract fun initView(savedInstanceState: Bundle?)

    /**
     *  获取bundle 中的数据
     */
    open fun getBundleExtras(extras: Bundle) {}


    /**
     * 是否有切换动画
     */
    protected open fun toggleOverridePendingTransition() = false

    /**
     * 获得切换动画的模式
     */
    protected open fun getOverridePendingTransition(): TransitionMode? = null

    /**
     * 获取当前布局id
     */
    abstract fun getContentViewLayoutId(): Int


}




================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseAppCompatFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.jennifer.andy.simpleeyes.widget.state.MultipleStateView
import me.yokeyword.fragmentation.SupportFragment


/**
 * Author:  andy.xwt
 * Date:    2017/9/5 19:06
 * Description:
 */

abstract class BaseAppCompatFragment : SupportFragment() {

    protected lateinit var LOG_TAG: String
    protected lateinit var mMultipleStateView: MultipleStateView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        LOG_TAG = this.javaClass.simpleName
        if (arguments != null) {
            getBundleExtras(arguments!!)
        }
    }


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return if (getContentViewLayoutId() != 0) {
            mMultipleStateView = MultipleStateView(context!!)
            return View.inflate(context, getContentViewLayoutId(), mMultipleStateView)
        } else {
            super.onCreateView(inflater, container, savedInstanceState)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView(savedInstanceState)
    }


    /**
     * 获取bundle中相应data
     */
    open fun getBundleExtras(extras: Bundle) {}

    /**
     * 获取资源id
     */
    abstract fun getContentViewLayoutId(): Int

    /**
     * 初始化view
     */
    open fun initView(savedInstanceState: Bundle?) {

    }
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseFragment.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.jennifer.andy.simpleeyes.ui.base.presenter.BasePresenter
import com.jennifer.andy.simpleeyes.utils.getGenericInstance


/**
 * Author:  andy.xwt
 * Date:    2017/9/5 19:06
 * Description:
 */

@Suppress("UNCHECKED_CAST")
abstract class BaseFragment<V, T : BasePresenter<V>> : BaseAppCompatFragment(), BaseView {

    protected lateinit var mPresenter: T


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mPresenter = getGenericInstance(this, 1)
    }


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mPresenter.attachView(this as V, this)
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun showLoading() {
        mMultipleStateView.showLoading()
    }

    override fun showNetError(onClickListener: View.OnClickListener) {
        mMultipleStateView.showNetError(onClickListener)
    }

    override fun showEmpty(onClickListener: View.OnClickListener) {
        mMultipleStateView.showEmpty(onClickListener)
    }

    override fun showContent() {
        mMultipleStateView.showContent()
    }


}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseFragmentItemAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter


/**
 * Author:  andy.xwt
 * Date:    2018/7/4 13:59
 * Description:基础FragmentPagerAdapter适配器
 */

open class BaseFragmentItemAdapter(fragmentManager: FragmentManager,
                                   private val fragments: MutableList<Fragment>,
                                   private val titles: MutableList<String>) : FragmentPagerAdapter(fragmentManager) {

    override fun getItem(position: Int) = fragments[position]

    override fun getCount() = fragments.size

    override fun getPageTitle(position: Int) = titles[position]

}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base

import android.view.View


/**
 * Author:  andy.xwt
 * Date:    2017/9/5 19:09
 * Description:基础view
 */

interface BaseView {
    /**
     * 显示正在加载
     */
    fun showLoading()

    /**
     * 显示内容
     */
    fun showContent()

    /**
     * 显示网络异常
     */
    fun showNetError(onClickListener: View.OnClickListener)

    /**
     * 显示空界面
     */
    fun showEmpty(onClickListener: View.OnClickListener)
}

================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/LoadMoreView.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base


/**
 * Author:  andy.xwt
 * Date:    2018/6/30 01:50
 * Description:
 */
interface LoadMoreView<T> : BaseView {

    /**
     * 加载更多信息成功
     */
    fun loadMoreSuccess(data: T)

    /**
     * 没有更多
     */
    fun showNoMore()
}


================================================
FILE: app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/adapter/BaseDataAdapter.kt
================================================
package com.jennifer.andy.simpleeyes.ui.base.adapter

import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseQuickAdapter.OnItemClickListener
import com.chad.library.adapter.base.BaseViewHolder
import com.chad.library.adapter.base.util.MultiTypeDelegate
import com.facebook.drawee.view.SimpleDraweeView
import com.jennifer.andy.simpleeyes.R
import com.jennifer.andy.simpleeyes.entity.Content
import com.jennifer.andy.simpleeyes.entity.ContentBean
import com.jennifer.andy.simpleeyes.ui.home.adapter.CollectionCardCoverAdapter
import com.jennifer.andy.simpleeyes.ui.home.adapter.SquareCollectionAdapter
import com.jennifer.andy.simpleeyes.ui.search.adapter.CollectionBriefAdapter
import com.jennifer.andy.simpleeyes.ui.video.VideoDetailActivity
import com.jennifer.andy.simpleeyes.utils.dip2px
import com.jennifer.andy.simpleeyes.utils.getElapseTimeForShow
import com.jennifer.andy.simpleeyes.widget.EliteImageView
import com.jennifer.andy.simpleeyes.widget.ItemHeaderView
import com.jennifer.andy.simpleeyes.widget.font.CustomFontTextView
import com.jennifer.andy.simpleeyes.widget.font.FontType
import com.jennifer.andy.simpleeyes.widget.image.imageloader.FrescoImageLoader
import com.jennifer.andy.simpleeyes.widget.viewpager.MarginWithIndicatorViewPager
import com.youth.banner.Banner
import com.youth.banner.BannerConfig
import java.util.*


/**
 * Author:  andy.xwt
 * Date:    2017/10/27 16:35
 * Description: 基础信息适配器
 */

open class BaseDataAdapter(data: MutableList<Content>) : BaseQuickAdapter<Content, BaseViewHolder>(data) {


    /**
     * 卡片类型
     */
    companion object {
        const val BANNER_TYPE = 0
        const val FOLLOW_CARD_TYPE = 1
        const val HORIZONTAL_SCROLL_CARD_TYPE = 2
        const val VIDEO_COLLECTION_WITH_COVER_TYPE = 3
        const val SQUARE_CARD_COLLECTION_TYPE = 4
        const val VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE = 5
        const val VIDEO_COLLECTION_WITH_BRIEF_TYPE = 6
        const val TEXT_CARD_TYPE = 7
        const val BRIEF_CARD_TYPE = 8
        const val BLANK_CARD_TYPE = 9
        const val SQUARE_CARD_TYPE = 10
        const val RECTANGLE_CARD_TYPE = 11
        const val VIDEO_TYPE = 12
        const val VIDEO_BANNER_THREE_TYPE = 13
        const val VIDEO_SMALL_CARD_TYPE = 14

        const val BANNER = "banner"
        const val FOLLOW_CARD = "followCard"
        const val HORIZONTAL_CARD = "horizontalScrollCard"
        const val VIDEO_COLLECTION_WITH_COVER = "videoCollectionWithCover"
        const val SQUARE_CARD_COLLECTION = "squareCardCollection"
        const val VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD = "videoCollectionOfHorizontalScrollCard"
        const val VIDEO_COLLECTION_WITH_BRIEF = "videoCollectionWithBrief"
        const val TEXT_CARD = "textCard"
        const val BRIEF_CARD = "briefCard"
        const val BLANK_CARD = "blankCard"
        const val SQUARE_CARD = "squareCard"
        const val RECTANGLE_CARD = "rectangleCard"
        const val VIDEO = "video"
        const val VIDEO_BANNER_THREE = "banner3"
        const val VIDEO_SMALL_CARD = "videoSmallCard"
    }


    init {
        multiTypeDelegate = object : MultiTypeDelegate<Content>() {
            override fun getItemType(andyInfoItem: Content?): Int {
                when (andyInfoItem?.type) {
                    BANNER -> return BANNER_TYPE
                    FOLLOW_CARD -> return FOLLOW_CARD_TYPE
                    HORIZONTAL_CARD -> return HORIZONTAL_SCROLL_CARD_TYPE
                    VIDEO_COLLECTION_WITH_COVER -> return VIDEO_COLLECTION_WITH_COVER_TYPE
                    SQUARE_CARD_COLLECTION -> return SQUARE_CARD_COLLECTION_TYPE
                    VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD -> return VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE
                    VIDEO_COLLECTION_WITH_BRIEF -> return VIDEO_COLLECTION_WITH_BRIEF_TYPE
                    TEXT_CARD -> return TEXT_CARD_TYPE
                    BRIEF_CARD -> return BRIEF_CARD_TYPE
                    BLANK_CARD -> return BLANK_CARD_TYPE
                    SQUARE_CARD -> return SQUARE_CARD_TYPE
                    RECTANGLE_CARD -> return RECTANGLE_CARD_TYPE
                    VIDEO -> return VIDEO_TYPE
                    VIDEO_BANNER_THREE -> return VIDEO_BANNER_THREE_TYPE
                    VIDEO_SMALL_CARD -> return VIDEO_SMALL_CARD_TYPE

                }
                return FOLLOW_CARD_TYPE
            }
        }
        with(multiTypeDelegate) {
            registerItemType(BANNER_TYPE, R.layout.layout_card_banner)
            registerItemType(FOLLOW_CARD_TYPE, R.layout.layout_follow_card)
            registerItemType(HORIZONTAL_SCROLL_CARD_TYPE, R.layout.layout_horizontal_scroll_card)
            registerItemType(VIDEO_COLLECTION_WITH_COVER_TYPE, R.layout.layout_collection_with_cover)
            registerItemType(SQUARE_CARD_COLLECTION_TYPE, R.layout.layout_square_collection)
            registerItemType(VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE, R.layout.item_collection_of_horizontal_scroll_card)
            registerItemType(VIDEO_COLLECTION_WITH_BRIEF_TYPE, R.layout.layout_collection_with_brief)
            registerItemType(TEXT_CARD_TYPE, R.layout.layout_single_text)
            registerItemType(BRIEF_CARD_TYPE, R.layout.layout_brife_card)
            registerItemType(BLANK_CARD_TYPE, R.layout.layout_blank_card)
            registerItemType(SQUARE_CARD_TYPE, R.layout.item_square_card)
            registerItemType(RECTANGLE_CARD_TYPE, R.layout.item_square_card)
            registerItemType(VIDEO_TYPE, R.layout.layout_single_video)
            registerItemType(VIDEO_BANNER_THREE_TYPE, R.layout.layout_follow_card)
            registerItemType(VIDEO_SMALL_CARD_TYPE, R.layout.layout_video_small_card)
        }
        //设置倒数第5个开启预加载
        setPreLoadNumber(5)
    }

    override fun convert(helper: BaseViewHolder?, item: Content) {
        when (helper?.itemViewType) {
            BANNER_TYPE -> setBannerInfo(helper, item)
            FOLLOW_CARD_TYPE -> setFollowCardInfo(helper, item)
            HORIZONTAL_SCROLL_CARD_TYPE -> setHorizontalScrollCardInfo(helper, item.data.itemList)
            VIDEO_COLLECTION_WITH_COVER_TYPE -> setCollectionCardWithCoverInfo(helper, item.data)
            SQUARE_CARD_COLLECTION_TYPE -> setSquareCollectionInfo(helper, item.data)
            VIDEO_COLLECTION_OF_HORIZONTAL_SCROLL_CARD_TYPE -> setCollectionOfHorizontalScrollCardInfo(helper, item)
            VIDEO_COLLECTION_WITH_BRIEF_TYPE -> setCollectionBriefInfo(helper, item)
            TEXT_CARD_TYPE -> setSingleText(helper, item)
            BRIEF_CARD_TYPE -> setBriefCardInfo(helper, item)
            BLANK_CARD_TYPE -> setBlankCardInfo(helper, item)
            SQUARE_CARD_TYPE -> setSquareCardInfo(helper, item.data)
            RECTANGLE_CARD_TYPE -> setRectangleCardInfo(helper, item.data)
            VIDEO_TYPE -> setSingleVideoInfo(helper, item)
            VIDEO_BANNER_THREE_TYPE -> setBanner3Info(helper, item.data)
            VIDEO_SMALL_CARD_TYPE -> setSmallCardInfo(helper, item.data)
        }

    }


    /**
     * 设置视频banner信息
     */
    private fun setBannerInfo(helper: BaseViewHolder, item: Content) {
        with(helper) {
            getView<SimpleDraweeView>(R.id.iv_image).setImageURI(item.data.image)
            itemView.setOnClickListener {
                if (!TextUtils.isEmpty(item.data.actionUrl)) {
                    mContext.startActivity(Intent(Intent.ACTION_VIEW).apply 
Download .txt
gitextract_epk5pk4i/

├── .circleci/
│   └── config.yml
├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── dictionaries/
│   │   └── andy.xml
│   ├── encodings.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── jennifer/
│       │               └── andy/
│       │                   └── simpleeyes/
│       │                       └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── assets/
│       │   │   └── fonts/
│       │   │       └── Lobster-1.4.otf
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── jennifer/
│       │   │           └── andy/
│       │   │               └── simpleeyes/
│       │   │                   ├── AndyApplication.kt
│       │   │                   ├── UserPreferences.kt
│       │   │                   ├── entity/
│       │   │                   │   └── AndyInfo.kt
│       │   │                   ├── manager/
│       │   │                   │   └── ActivityManager.kt
│       │   │                   ├── net/
│       │   │                   │   ├── Api.kt
│       │   │                   │   ├── ApiService.kt
│       │   │                   │   ├── Extras.kt
│       │   │                   │   └── RetrofitConfig.kt
│       │   │                   ├── player/
│       │   │                   │   ├── FileMediaDataSource.kt
│       │   │                   │   ├── IjkMediaController.kt
│       │   │                   │   ├── IjkVideoView.kt
│       │   │                   │   ├── IjkVideoViewWrapper.kt
│       │   │                   │   ├── MeasureHelper.kt
│       │   │                   │   ├── PolicyCompat.kt
│       │   │                   │   ├── event/
│       │   │                   │   │   └── VideoProgressEvent.kt
│       │   │                   │   ├── render/
│       │   │                   │   │   ├── IRenderView.kt
│       │   │                   │   │   ├── SurfaceRenderView.kt
│       │   │                   │   │   └── TextureRenderView.kt
│       │   │                   │   └── view/
│       │   │                   │       ├── ControllerView.kt
│       │   │                   │       ├── ControllerViewFactory.kt
│       │   │                   │       ├── ErrorView.kt
│       │   │                   │       ├── FullScreenControllerView.kt
│       │   │                   │       └── TinyControllerView.kt
│       │   │                   ├── router/
│       │   │                   │   ├── EyesPathReplaceService.kt
│       │   │                   │   ├── RouteIntercept.kt
│       │   │                   │   └── SchemeFilterActivity.kt
│       │   │                   ├── rx/
│       │   │                   │   ├── RxBus.kt
│       │   │                   │   ├── RxThreadHelper.kt
│       │   │                   │   └── error/
│       │   │                   │       ├── FlowableRetryDelay.kt
│       │   │                   │       ├── GlobalErrorProcessor.kt
│       │   │                   │       ├── GlobalErrorTransformer.kt
│       │   │                   │       ├── ObservabeRetryDelay.kt
│       │   │                   │       └── RetryConfig.kt
│       │   │                   ├── ui/
│       │   │                   │   ├── MainActivity.kt
│       │   │                   │   ├── author/
│       │   │                   │   │   ├── AuthorTagDetailActivity.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── AuthorModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   └── AuthorTagDetailPresenter.kt
│       │   │                   │   │   └── ui/
│       │   │                   │   │       └── AuthorTagDetailView.kt
│       │   │                   │   ├── base/
│       │   │                   │   │   ├── BaseActivity.kt
│       │   │                   │   │   ├── BaseAppCompatActivity.kt
│       │   │                   │   │   ├── BaseAppCompatFragment.kt
│       │   │                   │   │   ├── BaseFragment.kt
│       │   │                   │   │   ├── BaseFragmentItemAdapter.kt
│       │   │                   │   │   ├── BaseView.kt
│       │   │                   │   │   ├── LoadMoreView.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   └── BaseDataAdapter.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── BaseModel.kt
│       │   │                   │   │   └── presenter/
│       │   │                   │   │       ├── BasePresenter.kt
│       │   │                   │   │       └── LoadMorePresenter.kt
│       │   │                   │   ├── common/
│       │   │                   │   │   ├── CommonModel.kt
│       │   │                   │   │   ├── CommonPresenter.kt
│       │   │                   │   │   ├── CommonRecyclerActivity.kt
│       │   │                   │   │   └── CommonView.kt
│       │   │                   │   ├── feed/
│       │   │                   │   │   ├── AllCategoryActivity.kt
│       │   │                   │   │   ├── CategoryTabActivity.kt
│       │   │                   │   │   ├── FeedFragment.kt
│       │   │                   │   │   ├── RankListActivity.kt
│       │   │                   │   │   ├── TagActivity.kt
│       │   │                   │   │   ├── TagDetailInfoFragment.kt
│       │   │                   │   │   ├── TopicActivity.kt
│       │   │                   │   │   ├── WebViewActivity.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── FeedModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── AllCategoryPresenter.kt
│       │   │                   │   │   │   ├── CategoryTabPresenter.kt
│       │   │                   │   │   │   ├── FeedPresenter.kt
│       │   │                   │   │   │   ├── RankListPresenter.kt
│       │   │                   │   │   │   ├── TagDetailInfoPresenter.kt
│       │   │                   │   │   │   └── TopicPresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── AllCategoryView.kt
│       │   │                   │   │       ├── CategoryTabView.kt
│       │   │                   │   │       ├── FeedView.kt
│       │   │                   │   │       ├── RankListView.kt
│       │   │                   │   │       ├── TagDetailInfoView.kt
│       │   │                   │   │       └── TopicView.kt
│       │   │                   │   ├── follow/
│       │   │                   │   │   ├── AllAuthorActivity.kt
│       │   │                   │   │   ├── FollowFragment.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── FollowModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── AllAuthorPresenter.kt
│       │   │                   │   │   │   └── FollowPresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── AllAuthorView.kt
│       │   │                   │   │       └── FollowView.kt
│       │   │                   │   ├── home/
│       │   │                   │   │   ├── DailyEliteActivity.kt
│       │   │                   │   │   ├── HomeFragment.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   ├── CollectionCardCoverAdapter.kt
│       │   │                   │   │   │   ├── DailyEliteAdapter.kt
│       │   │                   │   │   │   └── SquareCollectionAdapter.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── HomeModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── DailyElitePresenter.kt
│       │   │                   │   │   │   └── HomePresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── DailyEliteView.kt
│       │   │                   │   │       └── HomeView.kt
│       │   │                   │   ├── login/
│       │   │                   │   │   └── LoginActivity.kt
│       │   │                   │   ├── profile/
│       │   │                   │   │   ├── CacheFragment.kt
│       │   │                   │   │   ├── ProfileFragment.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   └── ProfileSettingAdapter.kt
│       │   │                   │   │   ├── model/
│       │   │                   │   │   │   └── ProfileModel.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   ├── CachePresenter.kt
│       │   │                   │   │   │   └── ProfilePresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       ├── CacheView.kt
│       │   │                   │   │       └── ProfileView.kt
│       │   │                   │   ├── search/
│       │   │                   │   │   ├── SearchHotActivity.kt
│       │   │                   │   │   ├── adapter/
│       │   │                   │   │   │   ├── CollectionBriefAdapter.kt
│       │   │                   │   │   │   ├── SearchHotAdapter.kt
│       │   │                   │   │   │   └── SearchVideoAdapter.kt
│       │   │                   │   │   ├── presenter/
│       │   │                   │   │   │   └── SearchPresenter.kt
│       │   │                   │   │   └── view/
│       │   │                   │   │       └── SearchView.kt
│       │   │                   │   ├── splash/
│       │   │                   │   │   ├── LandingActivity.kt
│       │   │                   │   │   ├── LocalCommonLandingFragment.kt
│       │   │                   │   │   ├── SloganFragment.kt
│       │   │                   │   │   ├── VideoLandingFragment.kt
│       │   │                   │   │   └── adapter/
│       │   │                   │   │       └── SplashVideoFragmentAdapter.kt
│       │   │                   │   └── video/
│       │   │                   │       ├── VideoDetailActivity.kt
│       │   │                   │       ├── VideoInfoByIdActivity.kt
│       │   │                   │       ├── adapter/
│       │   │                   │       │   └── VideoDetailAdapter.kt
│       │   │                   │       ├── model/
│       │   │                   │       │   └── VideoDetailModel.kt
│       │   │                   │       ├── presenter/
│       │   │                   │       │   ├── VideoDetailPresenter.kt
│       │   │                   │       │   └── VideoInfoByIdPresenter.kt
│       │   │                   │       └── view/
│       │   │                   │           ├── VideoDetailView.kt
│       │   │                   │           └── VideoInfoByIdView.kt
│       │   │                   ├── utils/
│       │   │                   │   ├── AppUtils.kt
│       │   │                   │   ├── ButterKnife.kt
│       │   │                   │   ├── DensityUtils.kt
│       │   │                   │   ├── IntentRouterUtils.kt
│       │   │                   │   ├── KeyboardUtils.kt
│       │   │                   │   ├── NetWorkUtils.kt
│       │   │                   │   ├── ProcessUtils.kt
│       │   │                   │   ├── ScreenUtils.kt
│       │   │                   │   ├── SystemUtils.kt
│       │   │                   │   ├── TimeUtils.kt
│       │   │                   │   ├── ToastUtils.kt
│       │   │                   │   ├── UDIDUtils.kt
│       │   │                   │   ├── VideoPlayerUtils.kt
│       │   │                   │   └── ext/
│       │   │                   │       └── IntentEx.kt
│       │   │                   └── widget/
│       │   │                       ├── BottomBar.kt
│       │   │                       ├── BottomItem.kt
│       │   │                       ├── BottomItemLayout.kt
│       │   │                       ├── CollectionOfHorizontalScrollCardView.kt
│       │   │                       ├── CustomLoadMoreView.kt
│       │   │                       ├── EliteImageView.kt
│       │   │                       ├── FullScreenVideoView.kt
│       │   │                       ├── GridItemDecoration.kt
│       │   │                       ├── ItemHeaderView.kt
│       │   │                       ├── SearchHotRemindView.kt
│       │   │                       ├── StickyNavLayout.kt
│       │   │                       ├── VerticalProgressBar.kt
│       │   │                       ├── VideoDetailAuthorView.kt
│       │   │                       ├── font/
│       │   │                       │   ├── CustomFontTextView.kt
│       │   │                       │   ├── CustomFontTypeWriterTextView.kt
│       │   │                       │   ├── FontType.kt
│       │   │                       │   ├── PrintSpan.kt
│       │   │                       │   ├── PrintSpanGroup.kt
│       │   │                       │   └── TypefaceManager.kt
│       │   │                       ├── image/
│       │   │                       │   ├── CenterAlignImageSpan.kt
│       │   │                       │   └── imageloader/
│       │   │                       │       └── FrescoImageLoader.kt
│       │   │                       ├── pull/
│       │   │                       │   ├── head/
│       │   │                       │   │   ├── EliteHeaderView.kt
│       │   │                       │   │   ├── HeaderRefreshView.kt
│       │   │                       │   │   ├── HomePageHeaderView.kt
│       │   │                       │   │   └── VideoDetailHeadView.kt
│       │   │                       │   ├── refresh/
│       │   │                       │   │   ├── LinearLayoutManagerWithSmoothScroller.kt
│       │   │                       │   │   ├── PullRefreshView.kt
│       │   │                       │   │   ├── PullToRefresh.kt
│       │   │                       │   │   ├── PullToRefreshBase.kt
│       │   │                       │   │   └── PullToRefreshRecyclerView.kt
│       │   │                       │   └── zoom/
│       │   │                       │       ├── PullToZoom.kt
│       │   │                       │       ├── PullToZoomBase.kt
│       │   │                       │       └── PullToZoomRecyclerView.kt
│       │   │                       ├── state/
│       │   │                       │   ├── MultipleStateView.kt
│       │   │                       │   └── NetLoadingView.kt
│       │   │                       ├── tab/
│       │   │                       │   ├── AnimationUtils.java
│       │   │                       │   ├── ShortTabLayout.java
│       │   │                       │   └── TabItem.java
│       │   │                       └── viewpager/
│       │   │                           ├── InterceptVerticalViewPager.kt
│       │   │                           └── MarginWithIndicatorViewPager.kt
│       │   └── res/
│       │       ├── anim/
│       │       │   ├── bottom_in.xml
│       │       │   ├── bottom_out.xml
│       │       │   ├── fade_in.xml
│       │       │   ├── fade_out.xml
│       │       │   ├── left_in.xml
│       │       │   ├── left_out.xml
│       │       │   ├── no_anim.xml
│       │       │   ├── right_in.xml
│       │       │   ├── right_out.xml
│       │       │   ├── scale_in.xml
│       │       │   ├── scale_out.xml
│       │       │   ├── top_in.xml
│       │       │   └── top_out.xml
│       │       ├── color/
│       │       │   └── selector_item_square_text.xml
│       │       ├── drawable/
│       │       │   ├── ic_launcher_background.xml
│       │       │   ├── seek_bar_layer.xml
│       │       │   ├── selector_checkbox_bg.xml
│       │       │   ├── selector_item_square_foreground.xml
│       │       │   ├── shape_black_border.xml
│       │       │   ├── shape_border_bottom.xml
│       │       │   ├── shape_border_bottom_top.xml
│       │       │   ├── shape_hot_search_bg.xml
│       │       │   ├── shape_indicator_selected.xml
│       │       │   ├── shape_indicator_unselected.xml
│       │       │   ├── shape_share_bg.xml
│       │       │   ├── shape_show_all_border.xml
│       │       │   ├── shape_translate_border.xml
│       │       │   ├── shape_video_detail_placeholder.xml
│       │       │   ├── shape_webview_scrollbar.xml
│       │       │   └── shape_white_border.xml
│       │       ├── layout/
│       │       │   ├── activity_all_author.xml
│       │       │   ├── activity_all_category.xml
│       │       │   ├── activity_author_tag_detail.xml
│       │       │   ├── activity_category_tab.xml
│       │       │   ├── activity_common_recyclerview.xml
│       │       │   ├── activity_daily_elite.xml
│       │       │   ├── activity_landing.xml
│       │       │   ├── activity_login.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── activity_rank.xml
│       │       │   ├── activity_tag.xml
│       │       │   ├── activity_topic.xml
│       │       │   ├── activity_video_detail.xml
│       │       │   ├── activity_videoinfo_by_id.xml
│       │       │   ├── activity_webview.xml
│       │       │   ├── dialog_light_controller.xml
│       │       │   ├── dialog_volume_controller.xml
│       │       │   ├── empty_search_word.xml
│       │       │   ├── fragment_cache.xml
│       │       │   ├── fragment_feed.xml
│       │       │   ├── fragment_follow.xml
│       │       │   ├── fragment_home.xml
│       │       │   ├── fragment_local_coomon_landing.xml
│       │       │   ├── fragment_profile.xml
│       │       │   ├── fragment_search_hot.xml
│       │       │   ├── fragment_slogan.xml
│       │       │   ├── fragment_tag_detail_info.xml
│       │       │   ├── fragment_video_landing.xml
│       │       │   ├── item_collection_brief.xml
│       │       │   ├── item_collection_card_cover.xml
│       │       │   ├── item_collection_of_horizontal_scroll_card.xml
│       │       │   ├── item_hot_search.xml
│       │       │   ├── item_profile_setting.xml
│       │       │   ├── item_square_card.xml
│       │       │   ├── item_square_collection.xml
│       │       │   ├── item_the_end.xml
│       │       │   ├── item_video_samll_card.xml
│       │       │   ├── item_video_text_card.xml
│       │       │   ├── layout_author_tag_detail_header.xml
│       │       │   ├── layout_blank_card.xml
│       │       │   ├── layout_bottom_item.xml
│       │       │   ├── layout_brife_card.xml
│       │       │   ├── layout_card_banner.xml
│       │       │   ├── layout_category_head_view.xml
│       │       │   ├── layout_category_tab_toolbar.xml
│       │       │   ├── layout_center_title_share_toolbar.xml
│       │       │   ├── layout_choiceness.xml
│       │       │   ├── layout_collection_of_horizontal_scroll_card.xml
│       │       │   ├── layout_collection_with_brief.xml
│       │       │   ├── layout_collection_with_cover.xml
│       │       │   ├── layout_common_text.xml
│       │       │   ├── layout_common_toolbar.xml
│       │       │   ├── layout_division_line.xml
│       │       │   ├── layout_follow_card.xml
│       │       │   ├── layout_horizontal_scroll_card.xml
│       │       │   ├── layout_ijk_wrapper.xml
│       │       │   ├── layout_left_title_share_toolbar.xml
│       │       │   ├── layout_load_more_view.xml
│       │       │   ├── layout_loading_message.xml
│       │       │   ├── layout_loading_view.xml
│       │       │   ├── layout_margin_with_indicator_pager.xml
│       │       │   ├── layout_media_controller_full_screen.xml
│       │       │   ├── layout_media_controller_tiny.xml
│       │       │   ├── layout_search_hot_remind_view.xml
│       │       │   ├── layout_single_text.xml
│       │       │   ├── layout_single_video.xml
│       │       │   ├── layout_square_collection.xml
│       │       │   ├── layout_video_author_head.xml
│       │       │   ├── layout_video_detail_head.xml
│       │       │   ├── layout_video_error.xml
│       │       │   ├── layout_video_small_card.xml
│       │       │   ├── refresh_category_header.xml
│       │       │   └── refresh_daily_elite_header.xml
│       │       ├── layout-v21/
│       │       │   └── layout_square_collection.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── values/
│       │       │   ├── attrs.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── ids_sticky_nav_layout.xml
│       │       │   ├── strings.xml
│       │       │   └── styles.xml
│       │       └── xml/
│       │           └── network_security_config.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── jennifer/
│                       └── andy/
│                           └── simpleeyes/
│                               └── ExampleUnitTest.kt
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Download .txt
SYMBOL INDEX (152 symbols across 3 files)

FILE: app/src/main/java/com/jennifer/andy/simpleeyes/widget/tab/AnimationUtils.java
  class AnimationUtils (line 17) | public class AnimationUtils {
    method lerp (line 28) | static float lerp(float startValue, float endValue, float fraction) {
    method lerp (line 32) | static int lerp(int startValue, int endValue, float fraction) {

FILE: app/src/main/java/com/jennifer/andy/simpleeyes/widget/tab/ShortTabLayout.java
  class ShortTabLayout (line 135) | @ViewPager.DecorView
    type OnTabSelectedListener (line 208) | public interface OnTabSelectedListener {
      method onTabSelected (line 215) | public void onTabSelected(Tab tab);
      method onTabUnselected (line 222) | public void onTabUnselected(Tab tab);
      method onTabReselected (line 230) | public void onTabReselected(Tab tab);
    method ShortTabLayout (line 276) | public ShortTabLayout(Context context) {
    method ShortTabLayout (line 280) | public ShortTabLayout(Context context, AttributeSet attrs) {
    method ShortTabLayout (line 284) | public ShortTabLayout(Context context, AttributeSet attrs, int defStyl...
    method checkAppCompatTheme (line 366) | private void checkAppCompatTheme(Context context) {
    method setSelectedTabIndicatorColor (line 382) | public void setSelectedTabIndicatorColor(@ColorInt int color) {
    method setSelectedTabIndicatorHeight (line 392) | public void setSelectedTabIndicatorHeight(int height) {
    method setScrollPosition (line 406) | public void setScrollPosition(int position, float positionOffset, bool...
    method setScrollPosition (line 410) | void setScrollPosition(int position, float positionOffset, boolean upd...
    method getScrollPosition (line 434) | private float getScrollPosition() {
    method addTab (line 444) | public void addTab(@NonNull Tab tab) {
    method addTab (line 455) | public void addTab(@NonNull Tab tab, int position) {
    method addTab (line 465) | public void addTab(@NonNull Tab tab, boolean setSelected) {
    method addTab (line 476) | public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
    method addTabFromItemView (line 488) | private void addTabFromItemView(@NonNull TabItem item) {
    method setOnTabSelectedListener (line 509) | @Deprecated
    method addOnTabSelectedListener (line 533) | public void addOnTabSelectedListener(@NonNull OnTabSelectedListener li...
    method removeOnTabSelectedListener (line 545) | public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener...
    method clearOnTabSelectedListeners (line 552) | public void clearOnTabSelectedListeners() {
    method newTab (line 563) | @NonNull
    method getTabCount (line 579) | public int getTabCount() {
    method getTabAt (line 586) | @Nullable
    method getSelectedTabPosition (line 596) | public int getSelectedTabPosition() {
    method removeTab (line 606) | public void removeTab(Tab tab) {
    method removeTabAt (line 620) | public void removeTabAt(int position) {
    method removeAllTabs (line 643) | public void removeAllTabs() {
    method setTabMode (line 673) | public void setTabMode(@Mode int mode) {
    method getTabMode (line 685) | @Mode
    method setTabGravity (line 696) | public void setTabGravity(@TabGravity int gravity) {
    method getTabGravity (line 708) | @TabGravity
    method setTabTextColors (line 718) | public void setTabTextColors(@Nullable ColorStateList textColor) {
    method getTabTextColors (line 728) | @Nullable
    method setTabTextColors (line 739) | public void setTabTextColors(int normalColor, int selectedColor) {
    method setupWithViewPager (line 751) | public void setupWithViewPager(@Nullable ViewPager viewPager) {
    method setupWithViewPager (line 773) | public void setupWithViewPager(@Nullable final ViewPager viewPager, bo...
    method setupWithViewPager (line 777) | private void setupWithViewPager(@Nullable final ViewPager viewPager, b...
    method setTabsFromPagerAdapter (line 840) | @Deprecated
    method shouldDelayChildPressedState (line 845) | @Override
    method onAttachedToWindow (line 851) | @Override
    method onDetachedFromWindow (line 867) | @Override
    method getTabScrollRange (line 878) | private int getTabScrollRange() {
    method setPagerAdapter (line 883) | void setPagerAdapter(@Nullable final PagerAdapter adapter, final boole...
    method populateFromPagerAdapter (line 903) | void populateFromPagerAdapter() {
    method updateAllTabs (line 922) | private void updateAllTabs() {
    method createTabView (line 928) | private TabView createTabView(@NonNull final Tab tab) {
    method configureTab (line 939) | private void configureTab(Tab tab, int position) {
    method addTabView (line 949) | private void addTabView(Tab tab) {
    method addView (line 954) | @Override
    method addView (line 959) | @Override
    method addView (line 964) | @Override
    method addView (line 969) | @Override
    method addViewInternal (line 974) | private void addViewInternal(final View child) {
    method createLayoutParamsForTabs (line 982) | private LinearLayout.LayoutParams createLayoutParamsForTabs() {
    method updateTabViewLayoutParams (line 989) | private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
    method dpToPx (line 999) | int dpToPx(int dps) {
    method onMeasure (line 1003) | @Override
    method removeTabViewAt (line 1060) | private void removeTabViewAt(int position) {
    method animateToTab (line 1070) | private void animateToTab(int newPosition) {
    method ensureScrollAnimator (line 1097) | private void ensureScrollAnimator() {
    method setScrollAnimatorListener (line 1111) | void setScrollAnimatorListener(Animator.AnimatorListener listener) {
    method setSelectedTabView (line 1116) | private void setSelectedTabView(int position) {
    method selectTab (line 1126) | void selectTab(Tab tab) {
    method selectTab (line 1130) | void selectTab(final Tab tab, boolean updateIndicator) {
    method dispatchTabSelected (line 1162) | private void dispatchTabSelected(@NonNull final Tab tab) {
    method dispatchTabUnselected (line 1168) | private void dispatchTabUnselected(@NonNull final Tab tab) {
    method dispatchTabReselected (line 1174) | private void dispatchTabReselected(@NonNull final Tab tab) {
    method calculateScrollXForTab (line 1180) | private int calculateScrollXForTab(int position, float positionOffset) {
    method applyModeAndGravity (line 1201) | private void applyModeAndGravity() {
    method updateTabViews (line 1221) | void updateTabViews(final boolean requestLayout) {
    class Tab (line 1235) | public static final class Tab {
      method Tab (line 1254) | Tab() {
      method getTag (line 1261) | @Nullable
      method setTag (line 1272) | @NonNull
      method getCustomView (line 1285) | @Nullable
      method setCustomView (line 1303) | @NonNull
      method setCustomView (line 1323) | @NonNull
      method getIcon (line 1334) | @Nullable
      method getPosition (line 1345) | public int getPosition() {
      method setPosition (line 1349) | void setPosition(int position) {
      method getText (line 1358) | @Nullable
      method setIcon (line 1369) | @NonNull
      method setIcon (line 1382) | @NonNull
      method setText (line 1397) | @NonNull
      method setText (line 1411) | @NonNull
      method select (line 1422) | public void select() {
      method isSelected (line 1432) | public boolean isSelected() {
      method setContentDescription (line 1448) | @NonNull
      method setContentDescription (line 1465) | @NonNull
      method getContentDescription (line 1479) | @Nullable
      method updateView (line 1484) | void updateView() {
      method reset (line 1490) | void reset() {
    class TabView (line 1502) | class TabView extends LinearLayout {
      method TabView (line 1513) | public TabView(Context context) {
      method performClick (line 1528) | @Override
      method setSelected (line 1543) | @Override
      method onInitializeAccessibilityEvent (line 1567) | @Override
      method onInitializeAccessibilityNodeInfo (line 1574) | @Override
      method onMeasure (line 1581) | @Override
      method setTab (line 1647) | void setTab(@Nullable final Tab tab) {
      method reset (line 1654) | void reset() {
      method update (line 1659) | final void update() {
      method updateTextAndIcon (line 1725) | private void updateTextAndIcon(@Nullable final TextView textView,
      method getTab (line 1771) | public Tab getTab() {
      method approximateLineWidth (line 1778) | private float approximateLineWidth(Layout layout, int line, float te...
    class SlidingTabStrip (line 1783) | private class SlidingTabStrip extends LinearLayout {
      method SlidingTabStrip (line 1797) | SlidingTabStrip(Context context) {
      method setSelectedIndicatorColor (line 1803) | void setSelectedIndicatorColor(int color) {
      method setSelectedIndicatorHeight (line 1810) | void setSelectedIndicatorHeight(int height) {
      method childrenNeedLayout (line 1817) | boolean childrenNeedLayout() {
      method setIndicatorPositionFromTabPosition (line 1827) | void setIndicatorPositionFromTabPosition(int position, float positio...
      method getIndicatorPosition (line 1837) | float getIndicatorPosition() {
      method onRtlPropertiesChanged (line 1841) | @Override
      method onMeasure (line 1856) | @Override
      method onLayout (line 1913) | @Override
      method updateIndicatorPosition (line 1930) | private void updateIndicatorPosition() {
      method setIndicatorPosition (line 1953) | void setIndicatorPosition(int left, int right) {
      method animateIndicatorToPosition (line 1962) | void animateIndicatorToPosition(final int position, int duration) {
      method draw (line 2031) | @Override
    method createColorStateList (line 2047) | private static ColorStateList createColorStateList(int defaultColor, i...
    method getDefaultHeight (line 2064) | private int getDefaultHeight() {
    method getTabMinWidth (line 2076) | private int getTabMinWidth() {
    method generateLayoutParams (line 2085) | @Override
    method getTabMaxWidth (line 2094) | int getTabMaxWidth() {
    class TabLayoutOnPageChangeListener (line 2108) | public static class TabLayoutOnPageChangeListener implements ViewPager...
      method TabLayoutOnPageChangeListener (line 2113) | public TabLayoutOnPageChangeListener(ShortTabLayout tabLayout) {
      method onPageScrollStateChanged (line 2117) | @Override
      method onPageScrolled (line 2123) | @Override
      method onPageSelected (line 2141) | @Override
      method reset (line 2155) | void reset() {
    class ViewPagerOnTabSelectedListener (line 2164) | public static class ViewPagerOnTabSelectedListener implements OnTabSel...
      method ViewPagerOnTabSelectedListener (line 2167) | public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
      method onTabSelected (line 2171) | @Override
      method onTabUnselected (line 2176) | @Override
      method onTabReselected (line 2181) | @Override
    class PagerAdapterObserver (line 2187) | private class PagerAdapterObserver extends DataSetObserver {
      method PagerAdapterObserver (line 2188) | PagerAdapterObserver() {
      method onChanged (line 2191) | @Override
      method onInvalidated (line 2196) | @Override
    class AdapterChangeListener (line 2202) | private class AdapterChangeListener implements ViewPager.OnAdapterChan...
      method AdapterChangeListener (line 2205) | AdapterChangeListener() {
      method onAdapterChanged (line 2208) | @Override
      method setAutoRefresh (line 2216) | void setAutoRefresh(boolean autoRefresh) {

FILE: app/src/main/java/com/jennifer/andy/simpleeyes/widget/tab/TabItem.java
  class TabItem (line 18) | public final class TabItem extends View {
    method TabItem (line 23) | public TabItem(Context context) {
    method TabItem (line 27) | @SuppressLint("RestrictedApi")
Condensed preview — 313 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (745K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 926,
    "preview": "references:\n  cache_key: &cache_key\n    key: jars-{{ checksum \"build.gradle\" }}-{{ checksum  \"app/build.gradle\" }}-{{ ch"
  },
  {
    "path": ".gitignore",
    "chars": 118,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 3309,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <codeStyleSettings lan"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "chars": 153,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"Defaul"
  },
  {
    "path": ".idea/dictionaries/andy.xml",
    "chars": 83,
    "preview": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"andy\" />\n</component>"
  },
  {
    "path": ".idea/encodings.xml",
    "chars": 200,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\" defaultCharsetForPropertiesFil"
  },
  {
    "path": ".idea/gradle.xml",
    "chars": 704,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linke"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 3235,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"NullableNotNullManager\">\n    <option nam"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 369,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 564,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 167,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "LICENSE",
    "chars": 587,
    "preview": "   Copyright [2019] [AndyJennifer]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not us"
  },
  {
    "path": "README.md",
    "chars": 2947,
    "preview": "# SimpleEyes\n\n[![CircleCI](https://circleci.com/gh/AndyJennifer/SimpleEyes.svg?style=shield)](https://circleci.com/gh/An"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 3304,
    "preview": "apply plugin: 'com.android.application'\n\napply plugin: 'kotlin-android'\n\napply plugin: 'kotlin-android-extensions'\n\nappl"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 933,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "app/src/androidTest/java/com/jennifer/andy/simpleeyes/ExampleInstrumentedTest.kt",
    "chars": 654,
    "preview": "package com.jennifer.andy.simpleeyes\n\nimport androidx.test.InstrumentationRegistry\nimport androidx.test.runner.AndroidJU"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 6124,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/AndyApplication.kt",
    "chars": 1266,
    "preview": "package com.jennifer.andy.simpleeyes\n\nimport android.app.Application\nimport com.alibaba.android.arouter.launcher.ARouter"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/UserPreferences.kt",
    "chars": 1603,
    "preview": "package com.jennifer.andy.simpleeyes\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport and"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/entity/AndyInfo.kt",
    "chars": 7808,
    "preview": "package com.jennifer.andy.simpleeyes.entity\n\nimport java.io.Serializable\n\n/**\n * Author:  andy.xwt\n * Date:    2017/10/1"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/manager/ActivityManager.kt",
    "chars": 1905,
    "preview": "package com.jennifer.andy.simpleeyes.manager\n\nimport android.app.Activity\n\n\n/**\n * Author:  andy.xwt\n * Date:    2017/8/"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/net/Api.kt",
    "chars": 321,
    "preview": "package com.jennifer.andy.simpleeyes.net\n\n\n/**\n * Author:  andy.xwt\n * Date:    2017/10/15 09:54\n * Description:\n */\n\nob"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/net/ApiService.kt",
    "chars": 3897,
    "preview": "package com.jennifer.andy.simpleeyes.net\n\nimport com.jennifer.andy.simpleeyes.entity.*\nimport io.reactivex.Observable\nim"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/net/Extras.kt",
    "chars": 575,
    "preview": "package com.jennifer.andy.simpleeyes.net\n\n\n/**\n * Author:  andy.xwt\n * Date:    2017/10/31 14:16\n * Description:\n */\n\nob"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/net/RetrofitConfig.kt",
    "chars": 4040,
    "preview": "package com.jennifer.andy.simpleeyes.net\n\nimport com.google.gson.GsonBuilder\nimport com.jennifer.andy.simpleeyes.AndyApp"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/FileMediaDataSource.kt",
    "chars": 892,
    "preview": "package com.jennifer.andy.simpleeyes.player\n\nimport tv.danmaku.ijk.media.player.misc.IMediaDataSource\nimport java.io.Fil"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkMediaController.kt",
    "chars": 9146,
    "preview": "package com.jennifer.andy.simpleeyes.player\n\nimport android.app.Activity\nimport android.content.Context\nimport android.g"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkVideoView.kt",
    "chars": 26895,
    "preview": "package com.jennifer.andy.simpleeyes.player\n\nimport android.annotation.TargetApi\nimport android.content.Context\nimport a"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/IjkVideoViewWrapper.kt",
    "chars": 12348,
    "preview": "package com.jennifer.andy.simpleeyes.player\n\nimport android.app.Dialog\nimport android.content.Context\nimport android.con"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/MeasureHelper.kt",
    "chars": 8907,
    "preview": "package com.jennifer.andy.simpleeyes.player\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.player.render."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/PolicyCompat.kt",
    "chars": 1137,
    "preview": "package com.jennifer.andy.simpleeyes.player\n\nimport android.content.Context\nimport android.view.Window\n\n\n/**\n * Author: "
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/event/VideoProgressEvent.kt",
    "chars": 330,
    "preview": "package com.jennifer.andy.simpleeyes.player.event\n\n\n/**\n * Author:  andy.xwt\n * Date:    2020/3/9 10:02 PM\n * Descriptio"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/render/IRenderView.kt",
    "chars": 2103,
    "preview": "package com.jennifer.andy.simpleeyes.player.render\n\nimport android.graphics.SurfaceTexture\nimport android.view.Surface\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/render/SurfaceRenderView.kt",
    "chars": 7960,
    "preview": "package com.jennifer.andy.simpleeyes.player.render\n\nimport android.annotation.TargetApi\nimport android.content.Context\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/render/TextureRenderView.kt",
    "chars": 11988,
    "preview": "package com.jennifer.andy.simpleeyes.player.render\n\nimport android.annotation.TargetApi\nimport android.content.Context\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ControllerView.kt",
    "chars": 3702,
    "preview": "package com.jennifer.andy.simpleeyes.player.view\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimpo"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ControllerViewFactory.kt",
    "chars": 599,
    "preview": "package com.jennifer.andy.simpleeyes.player.view\n\nimport android.content.Context\n\n\n/**\n * Author:  andy.xwt\n * Date:    "
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/view/ErrorView.kt",
    "chars": 1452,
    "preview": "package com.jennifer.andy.simpleeyes.player.view\n\nimport android.animation.Animator\nimport android.animation.AnimatorLis"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/view/FullScreenControllerView.kt",
    "chars": 5200,
    "preview": "package com.jennifer.andy.simpleeyes.player.view\n\nimport android.content.Context\nimport android.view.View\nimport android"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/player/view/TinyControllerView.kt",
    "chars": 3364,
    "preview": "package com.jennifer.andy.simpleeyes.player.view\n\nimport android.content.Context\nimport android.view.View\nimport android"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/router/EyesPathReplaceService.kt",
    "chars": 6176,
    "preview": "package com.jennifer.andy.simpleeyes.router\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.core."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/router/RouteIntercept.kt",
    "chars": 1375,
    "preview": "package com.jennifer.andy.simpleeyes.router\n\nimport android.content.Context\nimport com.alibaba.android.arouter.facade.Po"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/router/SchemeFilterActivity.kt",
    "chars": 745,
    "preview": "package com.jennifer.andy.simpleeyes.router\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nim"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/RxBus.kt",
    "chars": 2574,
    "preview": "package com.jennifer.andy.simpleeyes.rx\n\nimport io.reactivex.Observable\nimport io.reactivex.android.schedulers.AndroidSc"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/RxThreadHelper.kt",
    "chars": 1425,
    "preview": "package com.jennifer.andy.simpleeyes.rx\n\nimport io.reactivex.FlowableTransformer\nimport io.reactivex.MaybeTransformer\nim"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/FlowableRetryDelay.kt",
    "chars": 1226,
    "preview": "package com.jennifer.andy.simpleeyes.rx.error\n\nimport io.reactivex.Flowable\nimport io.reactivex.functions.Function\nimpor"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/GlobalErrorProcessor.kt",
    "chars": 837,
    "preview": "package com.jennifer.andy.simpleeyes.rx.error\n\nimport com.jennifer.andy.simpleeyes.utils.toast\nimport retrofit2.HttpExce"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/GlobalErrorTransformer.kt",
    "chars": 3466,
    "preview": "package com.jennifer.andy.simpleeyes.rx.error\n\nimport io.reactivex.*\n\n\n/**\n * Author:  andy.xwt\n * Date:    2019-11-17 2"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/ObservabeRetryDelay.kt",
    "chars": 1227,
    "preview": "package com.jennifer.andy.simpleeyes.rx.error\n\nimport io.reactivex.Observable\nimport io.reactivex.ObservableSource\nimpor"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/rx/error/RetryConfig.kt",
    "chars": 522,
    "preview": "package com.jennifer.andy.simpleeyes.rx.error\n\nimport io.reactivex.Single\n\n\n/**\n * Author:  andy.xwt\n * Date:    2019-11"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/MainActivity.kt",
    "chars": 3091,
    "preview": "package com.jennifer.andy.simpleeyes.ui\n\nimport android.os.Bundle\nimport com.jennifer.andy.simpleeyes.R\nimport com.jenni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/AuthorTagDetailActivity.kt",
    "chars": 4741,
    "preview": "package com.jennifer.andy.simpleeyes.ui.author\n\nimport android.animation.ArgbEvaluator\nimport android.graphics.Color\nimp"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/model/AuthorModel.kt",
    "chars": 744,
    "preview": "package com.jennifer.andy.simpleeyes.ui.author.model\n\nimport com.jennifer.andy.simpleeyes.entity.Tab\nimport com.jennifer"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/presenter/AuthorTagDetailPresenter.kt",
    "chars": 843,
    "preview": "package com.jennifer.andy.simpleeyes.ui.author.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.u"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/author/ui/AuthorTagDetailView.kt",
    "chars": 306,
    "preview": "package com.jennifer.andy.simpleeyes.ui.author.ui\n\nimport com.jennifer.andy.simpleeyes.entity.Tab\nimport com.jennifer.an"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseActivity.kt",
    "chars": 1148,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\nimport android.os.Bundle\nimport android.view.View\nimport com.jennifer.andy"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseAppCompatActivity.kt",
    "chars": 4866,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.vie"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseAppCompatFragment.kt",
    "chars": 1613,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseFragment.kt",
    "chars": 1359,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseFragmentItemAdapter.kt",
    "chars": 723,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.Fragmen"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/BaseView.kt",
    "chars": 455,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\nimport android.view.View\n\n\n/**\n * Author:  andy.xwt\n * Date:    2017/9/5 1"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/LoadMoreView.kt",
    "chars": 277,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base\n\n\n/**\n * Author:  andy.xwt\n * Date:    2018/6/30 01:50\n * Description:\n */\n"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/adapter/BaseDataAdapter.kt",
    "chars": 16710,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base.adapter\n\nimport android.content.Intent\nimport android.net.Uri\nimport androi"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/model/BaseModel.kt",
    "chars": 1286,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base.model\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/presenter/BasePresenter.kt",
    "chars": 663,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base.presenter\n\nimport androidx.lifecycle.LifecycleObserver\nimport androidx.life"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/base/presenter/LoadMorePresenter.kt",
    "chars": 1220,
    "preview": "package com.jennifer.andy.simpleeyes.ui.base.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonModel.kt",
    "chars": 212,
    "preview": "package com.jennifer.andy.simpleeyes.ui.common\n\nimport com.jennifer.andy.simpleeyes.ui.base.model.BaseModel\n\n\n/**\n * Aut"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonPresenter.kt",
    "chars": 802,
    "preview": "package com.jennifer.andy.simpleeyes.ui.common\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.entity.Andy"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonRecyclerActivity.kt",
    "chars": 2225,
    "preview": "package com.jennifer.andy.simpleeyes.ui.common\n\nimport android.os.Bundle\nimport android.widget.RelativeLayout\nimport and"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/common/CommonView.kt",
    "chars": 389,
    "preview": "package com.jennifer.andy.simpleeyes.ui.common\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jennifer."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/AllCategoryActivity.kt",
    "chars": 2578,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/CategoryTabActivity.kt",
    "chars": 4697,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.animation.ArgbEvaluator\nimport android.graphics.Color\nimpor"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/FeedFragment.kt",
    "chars": 2972,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.os.Bundle\n\nimport android.view.View\nimport android.widget.I"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/RankListActivity.kt",
    "chars": 2992,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Re"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/TagActivity.kt",
    "chars": 2768,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.os.Bundle\nimport android.widget.RelativeLayout\nimport andro"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/TagDetailInfoFragment.kt",
    "chars": 2422,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.os.Bundle\nimport androidx.recyclerview.widget.LinearLayoutM"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/TopicActivity.kt",
    "chars": 2592,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Re"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/WebViewActivity.kt",
    "chars": 2363,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed\n\nimport android.os.Build\nimport android.os.Bundle\nimport android.webkit.Web"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/model/FeedModel.kt",
    "chars": 1929,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.model\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/AllCategoryPresenter.kt",
    "chars": 820,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/CategoryTabPresenter.kt",
    "chars": 800,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/FeedPresenter.kt",
    "chars": 810,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/RankListPresenter.kt",
    "chars": 755,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/TagDetailInfoPresenter.kt",
    "chars": 1035,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ent"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/presenter/TopicPresenter.kt",
    "chars": 904,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ent"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/AllCategoryView.kt",
    "chars": 326,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jennif"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/CategoryTabView.kt",
    "chars": 351,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.view\n\nimport com.jennifer.andy.simpleeyes.entity.Category\nimport com.jennif"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/FeedView.kt",
    "chars": 307,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.view\n\nimport com.jennifer.andy.simpleeyes.entity.TabInfo\nimport com.jennife"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/RankListView.kt",
    "chars": 311,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.view\n\nimport com.jennifer.andy.simpleeyes.entity.TabInfo\nimport com.jennife"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/TagDetailInfoView.kt",
    "chars": 343,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jennif"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/feed/view/TopicView.kt",
    "chars": 401,
    "preview": "package com.jennifer.andy.simpleeyes.ui.feed.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jennif"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/AllAuthorActivity.kt",
    "chars": 2224,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/FollowFragment.kt",
    "chars": 3144,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/model/FollowModel.kt",
    "chars": 999,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow.model\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jen"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/presenter/AllAuthorPresenter.kt",
    "chars": 930,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.e"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/presenter/FollowPresenter.kt",
    "chars": 1252,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.e"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/view/AllAuthorView.kt",
    "chars": 336,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenn"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/follow/view/FollowView.kt",
    "chars": 382,
    "preview": "package com.jennifer.andy.simpleeyes.ui.follow.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenn"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/DailyEliteActivity.kt",
    "chars": 4277,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Im"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/HomeFragment.kt",
    "chars": 3950,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home\n\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport android.widg"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/adapter/CollectionCardCoverAdapter.kt",
    "chars": 1574,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.adapter\n\nimport android.widget.TextView\nimport com.chad.library.adapter.bas"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/adapter/DailyEliteAdapter.kt",
    "chars": 577,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.adapter\n\nimport com.jennifer.andy.simpleeyes.entity.Content\nimport com.jenn"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/adapter/SquareCollectionAdapter.kt",
    "chars": 1597,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.adapter\n\nimport android.widget.FrameLayout\nimport com.chad.library.adapter."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/model/HomeModel.kt",
    "chars": 1989,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.model\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/presenter/DailyElitePresenter.kt",
    "chars": 2189,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ent"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/presenter/HomePresenter.kt",
    "chars": 1981,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/view/DailyEliteView.kt",
    "chars": 420,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.view\n\nimport com.jennifer.andy.simpleeyes.entity.Content\nimport com.jennife"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/home/view/HomeView.kt",
    "chars": 544,
    "preview": "package com.jennifer.andy.simpleeyes.ui.home.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jennif"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/login/LoginActivity.kt",
    "chars": 721,
    "preview": "package com.jennifer.andy.simpleeyes.ui.login\n\nimport android.os.Bundle\nimport android.widget.ImageView\nimport com.aliba"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/CacheFragment.kt",
    "chars": 1203,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/ProfileFragment.kt",
    "chars": 2521,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/adapter/ProfileSettingAdapter.kt",
    "chars": 532,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile.adapter\n\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport co"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/model/ProfileModel.kt",
    "chars": 221,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile.model\n\nimport com.jennifer.andy.simpleeyes.ui.base.model.BaseModel\n\n\n/**"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/presenter/CachePresenter.kt",
    "chars": 319,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile.presenter\n\nimport com.jennifer.andy.simpleeyes.ui.base.presenter.BasePre"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/presenter/ProfilePresenter.kt",
    "chars": 325,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile.presenter\n\nimport com.jennifer.andy.simpleeyes.ui.base.presenter.BasePre"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/view/CacheView.kt",
    "chars": 219,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile.view\n\nimport com.jennifer.andy.simpleeyes.ui.base.BaseView\n\n\n/**\n * Auth"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/profile/view/ProfileView.kt",
    "chars": 219,
    "preview": "package com.jennifer.andy.simpleeyes.ui.profile.view\n\nimport com.jennifer.andy.simpleeyes.ui.base.BaseView\n\n\n/**\n * Auth"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/SearchHotActivity.kt",
    "chars": 7572,
    "preview": "package com.jennifer.andy.simpleeyes.ui.search\n\n\nimport android.animation.ValueAnimator\nimport android.graphics.drawable"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/adapter/CollectionBriefAdapter.kt",
    "chars": 978,
    "preview": "package com.jennifer.andy.simpleeyes.ui.search.adapter\n\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/adapter/SearchHotAdapter.kt",
    "chars": 519,
    "preview": "package com.jennifer.andy.simpleeyes.ui.search.adapter\n\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/adapter/SearchVideoAdapter.kt",
    "chars": 3486,
    "preview": "package com.jennifer.andy.simpleeyes.ui.search.adapter\n\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport a"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/presenter/SearchPresenter.kt",
    "chars": 1189,
    "preview": "package com.jennifer.andy.simpleeyes.ui.search.presenter\n\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/search/view/SearchView.kt",
    "chars": 475,
    "preview": "package com.jennifer.andy.simpleeyes.ui.search.view\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenn"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/splash/LandingActivity.kt",
    "chars": 806,
    "preview": "package com.jennifer.andy.simpleeyes.ui.splash\n\nimport android.os.Bundle\nimport com.jennifer.andy.simpleeyes.R\nimport co"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/splash/LocalCommonLandingFragment.kt",
    "chars": 4976,
    "preview": "package com.jennifer.andy.simpleeyes.ui.splash\n\nimport android.animation.AnimatorSet\nimport android.animation.ObjectAnim"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/splash/SloganFragment.kt",
    "chars": 555,
    "preview": "package com.jennifer.andy.simpleeyes.ui.splash\n\nimport android.os.Bundle\nimport com.jennifer.andy.simpleeyes.R\nimport co"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/splash/VideoLandingFragment.kt",
    "chars": 4931,
    "preview": "package com.jennifer.andy.simpleeyes.ui.splash\n\nimport android.os.Bundle\nimport androidx.lifecycle.Lifecycle\nimport andr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/splash/adapter/SplashVideoFragmentAdapter.kt",
    "chars": 608,
    "preview": "package com.jennifer.andy.simpleeyes.ui.splash.adapter\n\nimport androidx.fragment.app.FragmentManager\nimport androidx.fra"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/VideoDetailActivity.kt",
    "chars": 10769,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video\n\nimport android.content.Context\nimport android.content.Intent\nimport andro"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/VideoInfoByIdActivity.kt",
    "chars": 1777,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video\n\nimport android.os.Bundle\nimport com.alibaba.android.arouter.facade.Postca"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/adapter/VideoDetailAdapter.kt",
    "chars": 2272,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video.adapter\n\nimport com.chad.library.adapter.base.BaseQuickAdapter\nimport com."
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/model/VideoDetailModel.kt",
    "chars": 1105,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video.model\n\nimport com.jennifer.andy.simpleeyes.entity.AndyInfo\nimport com.jenn"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/presenter/VideoDetailPresenter.kt",
    "chars": 800,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video.presenter\n\nimport com.jennifer.andy.simpleeyes.ui.base.presenter.BasePrese"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/presenter/VideoInfoByIdPresenter.kt",
    "chars": 822,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video.presenter\n\nimport android.view.View\nimport com.jennifer.andy.simpleeyes.ui"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/view/VideoDetailView.kt",
    "chars": 437,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video.view\n\nimport com.jennifer.andy.simpleeyes.entity.Content\nimport com.jennif"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/ui/video/view/VideoInfoByIdView.kt",
    "chars": 333,
    "preview": "package com.jennifer.andy.simpleeyes.ui.video.view\n\nimport com.jennifer.andy.simpleeyes.entity.ContentBean\nimport com.je"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/AppUtils.kt",
    "chars": 483,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.content.Context\n\n\n/**\n * 获取App版本号\n *\n * @return App版本号\n */\nfu"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/ButterKnife.kt",
    "chars": 6764,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.app.Activity\nimport android.app.Dialog\nimport android.app.Dia"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/DensityUtils.kt",
    "chars": 644,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.content.Context\n\n\n/**\n * 获取设备尺寸密度\n */\nfun Context.getDensity("
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/IntentRouterUtils.kt",
    "chars": 1545,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.app.Activity\nimport androidx.fragment.app.Fragment\nimport com"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/KeyboardUtils.kt",
    "chars": 635,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.app.Activity\nimport android.content.Context\nimport android.vi"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/NetWorkUtils.kt",
    "chars": 392,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.content.Context\nimport android.net.ConnectivityManager\n\n\n/**\n"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/ProcessUtils.kt",
    "chars": 447,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.app.ActivityManager\nimport android.content.Context\n\n\n/**\n * 获"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/ScreenUtils.kt",
    "chars": 591,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.content.Context\nimport android.util.DisplayMetrics\nimport and"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/SystemUtils.kt",
    "chars": 937,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport com.jennifer.andy.simpleeyes.ui.base.model.BaseModel\nimport com.jenni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/TimeUtils.kt",
    "chars": 2997,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport java.text.SimpleDateFormat\nimport java.util.*\n\n\n/**\n * 将秒数转换为 00 00' "
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/ToastUtils.kt",
    "chars": 553,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.content.Context\nimport android.os.Handler\nimport android.widg"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/UDIDUtils.kt",
    "chars": 152,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport java.util.*\n\n/**\n * 获取随机uuid\n */\nfun getRandomUUID() = UUID.randomUUI"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/VideoPlayerUtils.kt",
    "chars": 1939,
    "preview": "package com.jennifer.andy.simpleeyes.utils\n\nimport android.app.Activity\nimport android.content.Context\nimport android.co"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/utils/ext/IntentEx.kt",
    "chars": 2591,
    "preview": "package com.jennifer.andy.simpleeyes.utils.ext\n\nimport android.content.Context\nimport android.content.Intent\nimport andr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/BottomBar.kt",
    "chars": 4295,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.graphics.Color\nimport android"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/BottomItem.kt",
    "chars": 1239,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.graphics.drawable.Drawable\n\n\n/**\n * Author:  andy.xwt\n * Dat"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/BottomItemLayout.kt",
    "chars": 3087,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nim"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/CollectionOfHorizontalScrollCardView.kt",
    "chars": 2020,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport andr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/CustomLoadMoreView.kt",
    "chars": 540,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport com.chad.library.adapter.base.loadmore.LoadMoreView\nimport com.jenni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/EliteImageView.kt",
    "chars": 2057,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.text.TextUtils\nimport android"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/FullScreenVideoView.kt",
    "chars": 1050,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport andr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/GridItemDecoration.kt",
    "chars": 1482,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.graphics.Rect\nimport android.view.View\nimport androidx.recyc"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/ItemHeaderView.kt",
    "chars": 6158,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.A"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/SearchHotRemindView.kt",
    "chars": 1324,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport andr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/StickyNavLayout.kt",
    "chars": 5992,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport andr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/VerticalProgressBar.kt",
    "chars": 1137,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport androi"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/VideoDetailAuthorView.kt",
    "chars": 2171,
    "preview": "package com.jennifer.andy.simpleeyes.widget\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.util"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/font/CustomFontTextView.kt",
    "chars": 1282,
    "preview": "package com.jennifer.andy.simpleeyes.widget.font\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/font/CustomFontTypeWriterTextView.kt",
    "chars": 860,
    "preview": "package com.jennifer.andy.simpleeyes.widget.font\n\nimport android.content.Context\nimport android.text.TextUtils\nimport an"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/font/FontType.kt",
    "chars": 512,
    "preview": "package com.jennifer.andy.simpleeyes.widget.font\n\n\n/**\n * Author:  andy.xwt\n * Date:    2017/11/2 14:41\n * Description:字"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/font/PrintSpan.kt",
    "chars": 447,
    "preview": "package com.jennifer.andy.simpleeyes.widget.font\n\nimport android.text.TextPaint\nimport android.text.style.MetricAffectin"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/font/PrintSpanGroup.kt",
    "chars": 2261,
    "preview": "package com.jennifer.andy.simpleeyes.widget.font\n\nimport android.animation.ObjectAnimator\nimport android.graphics.Color\n"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/font/TypefaceManager.kt",
    "chars": 2107,
    "preview": "package com.jennifer.andy.simpleeyes.widget.font\n\nimport android.content.Context\nimport android.graphics.Typeface\nimport"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/image/CenterAlignImageSpan.kt",
    "chars": 887,
    "preview": "package com.jennifer.andy.simpleeyes.widget.image\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport "
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/image/imageloader/FrescoImageLoader.kt",
    "chars": 638,
    "preview": "package com.jennifer.andy.simpleeyes.widget.image.imageloader\n\nimport android.content.Context\nimport android.net.Uri\nimp"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/head/EliteHeaderView.kt",
    "chars": 2939,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.head\n\nimport android.animation.ArgbEvaluator\nimport android.animation.V"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/head/HeaderRefreshView.kt",
    "chars": 3230,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.head\n\nimport android.animation.ValueAnimator\nimport android.content.Con"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/head/HomePageHeaderView.kt",
    "chars": 4540,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.head\n\nimport android.content.Context\nimport android.util.AttributeSet\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/head/VideoDetailHeadView.kt",
    "chars": 3795,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.head\n\nimport android.animation.ObjectAnimator\nimport android.content.Co"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/refresh/LinearLayoutManagerWithSmoothScroller.kt",
    "chars": 1614,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.refresh\n\nimport android.content.Context\nimport android.graphics.PointF\n"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/refresh/PullRefreshView.kt",
    "chars": 889,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.refresh\n\nimport android.content.Context\nimport android.util.AttributeSe"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/refresh/PullToRefresh.kt",
    "chars": 489,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.refresh\n\n\n/**\n * Author:  andy.xwt\n * Date:    2018/6/15 10:12\n * Descr"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/refresh/PullToRefreshBase.kt",
    "chars": 12460,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.refresh\n\nimport android.content.Context\nimport android.util.AttributeSe"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/refresh/PullToRefreshRecyclerView.kt",
    "chars": 2056,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.refresh\n\nimport android.content.Context\nimport android.util.AttributeSe"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/zoom/PullToZoom.kt",
    "chars": 794,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.zoom\n\nimport android.content.res.TypedArray\nimport android.view.View\n\n\n"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/zoom/PullToZoomBase.kt",
    "chars": 7552,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.zoom\n\nimport android.content.Context\nimport android.util.AttributeSet\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/pull/zoom/PullToZoomRecyclerView.kt",
    "chars": 4859,
    "preview": "package com.jennifer.andy.simpleeyes.widget.pull.zoom\n\nimport android.animation.ValueAnimator\nimport android.content.Con"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/state/MultipleStateView.kt",
    "chars": 5900,
    "preview": "package com.jennifer.andy.simpleeyes.widget.state\n\nimport android.content.Context\nimport android.util.AttributeSet\nimpor"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/state/NetLoadingView.kt",
    "chars": 1381,
    "preview": "package com.jennifer.andy.simpleeyes.widget.state\n\nimport android.animation.ObjectAnimator\nimport android.content.Contex"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/tab/AnimationUtils.java",
    "chars": 1391,
    "preview": "package com.jennifer.andy.simpleeyes.widget.tab;\n\nimport android.view.animation.DecelerateInterpolator;\nimport android.v"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/tab/ShortTabLayout.java",
    "chars": 83010,
    "preview": "package com.jennifer.andy.simpleeyes.widget.tab;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorLi"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/tab/TabItem.java",
    "chars": 1105,
    "preview": "package com.jennifer.andy.simpleeyes.widget.tab;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/viewpager/InterceptVerticalViewPager.kt",
    "chars": 2259,
    "preview": "package com.jennifer.andy.simpleeyes.widget.viewpager\n\nimport android.content.Context\nimport android.util.AttributeSet\ni"
  },
  {
    "path": "app/src/main/java/com/jennifer/andy/simpleeyes/widget/viewpager/MarginWithIndicatorViewPager.kt",
    "chars": 3180,
    "preview": "package com.jennifer.andy.simpleeyes.widget.viewpager\n\nimport android.content.Context\nimport android.util.AttributeSet\ni"
  },
  {
    "path": "app/src/main/res/anim/bottom_in.xml",
    "chars": 197,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "app/src/main/res/anim/bottom_out.xml",
    "chars": 197,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "app/src/main/res/anim/fade_in.xml",
    "chars": 191,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android"
  },
  {
    "path": "app/src/main/res/anim/fade_out.xml",
    "chars": 191,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android"
  },
  {
    "path": "app/src/main/res/anim/left_in.xml",
    "chars": 199,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "app/src/main/res/anim/left_out.xml",
    "chars": 199,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "app/src/main/res/anim/no_anim.xml",
    "chars": 187,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android"
  },
  {
    "path": "app/src/main/res/anim/right_in.xml",
    "chars": 197,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  },
  {
    "path": "app/src/main/res/anim/right_out.xml",
    "chars": 197,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    and"
  }
]

// ... and 113 more files (download for full content)

About this extraction

This page contains the full source code of the AndyJennifer/SimpleEyes GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 313 files (655.3 KB), approximately 170.5k tokens, and a symbol index with 152 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!