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
[](https://circleci.com/gh/AndyJennifer/SimpleEyes)
[](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
你可以根据自己的需要,选择不同的分支。
## 项目展示 ☘

## 闪光点 ✨✨
- 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
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
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[](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.