├── .gitignore ├── .idea ├── .gitignore ├── caches │ └── build_file_checksums.ser ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── jarRepositories.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── sampledata │ └── episode.json └── src │ ├── androidTest │ └── java │ │ └── soko │ │ └── ekibun │ │ └── bangumi │ │ └── SpanTest.kt │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ ├── com │ │ ├── awarmisland │ │ │ └── android │ │ │ │ └── richedittext │ │ │ │ ├── bean │ │ │ │ ├── FontStyle.java │ │ │ │ └── SpanPart.java │ │ │ │ └── view │ │ │ │ └── RichEditText.java │ │ ├── bumptech │ │ │ └── glide │ │ │ │ └── samples │ │ │ │ └── svg │ │ │ │ ├── SvgDecoder.java │ │ │ │ └── SvgDrawableTranscoder.java │ │ └── oubowu │ │ │ └── stickyitemdecoration │ │ │ ├── FullSpanUtil.java │ │ │ ├── OnStickyChangeListener.java │ │ │ ├── StickyHeadContainer.java │ │ │ └── StickyItemDecoration.java │ └── soko │ │ └── ekibun │ │ └── bangumi │ │ ├── App.kt │ │ ├── api │ │ ├── ApiHelper.kt │ │ ├── bangumi │ │ │ ├── Bangumi.kt │ │ │ └── bean │ │ │ │ ├── Character.kt │ │ │ │ ├── Collection.kt │ │ │ │ ├── Comment.kt │ │ │ │ ├── Episode.kt │ │ │ │ ├── Images.kt │ │ │ │ ├── MonoInfo.kt │ │ │ │ ├── Person.kt │ │ │ │ ├── Say.kt │ │ │ │ ├── Subject.kt │ │ │ │ ├── TimeLine.kt │ │ │ │ ├── Topic.kt │ │ │ │ ├── TopicPost.kt │ │ │ │ └── UserInfo.kt │ │ ├── github │ │ │ ├── Github.kt │ │ │ └── bean │ │ │ │ ├── BangumiCalendarItem.kt │ │ │ │ ├── BangumiLinkMap.kt │ │ │ │ ├── OnAirInfo.kt │ │ │ │ └── Release.kt │ │ └── sda1 │ │ │ ├── Sda1.kt │ │ │ └── bean │ │ │ └── Response.kt │ │ ├── model │ │ ├── DataCacheModel.kt │ │ ├── HistoryModel.kt │ │ ├── PluginsModel.kt │ │ ├── SearchHistoryModel.kt │ │ ├── ThemeModel.kt │ │ ├── UserModel.kt │ │ └── history │ │ │ ├── History.kt │ │ │ ├── HistoryDao.kt │ │ │ └── HistoryDatabase.kt │ │ ├── ui │ │ ├── action │ │ │ └── ActionActivity.kt │ │ ├── main │ │ │ ├── DrawerView.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainPresenter.kt │ │ │ ├── UserView.kt │ │ │ └── fragment │ │ │ │ ├── DrawerFragment.kt │ │ │ │ ├── calendar │ │ │ │ ├── CalendarAdapter.kt │ │ │ │ ├── CalendarFragment.kt │ │ │ │ ├── CalendarPagerAdapter.kt │ │ │ │ └── CalendarTabAdapter.kt │ │ │ │ ├── history │ │ │ │ ├── HistoryAdapter.kt │ │ │ │ └── HistoryFragment.kt │ │ │ │ ├── home │ │ │ │ ├── HomeFragment.kt │ │ │ │ └── fragment │ │ │ │ │ ├── HomeTabFragment.kt │ │ │ │ │ ├── collection │ │ │ │ │ ├── CollectTypeView.kt │ │ │ │ │ ├── CollectionFragment.kt │ │ │ │ │ ├── CollectionListAdapter.kt │ │ │ │ │ └── CollectionPagerAdapter.kt │ │ │ │ │ ├── rakuen │ │ │ │ │ ├── RakuenAdapter.kt │ │ │ │ │ ├── RakuenFragment.kt │ │ │ │ │ └── RakuenPagerAdapter.kt │ │ │ │ │ └── timeline │ │ │ │ │ ├── TimeLineAdapter.kt │ │ │ │ │ ├── TimeLineFragment.kt │ │ │ │ │ └── TimeLinePagerAdapter.kt │ │ │ │ └── index │ │ │ │ ├── IndexFragment.kt │ │ │ │ ├── IndexPagerAdapter.kt │ │ │ │ ├── IndexTypeView.kt │ │ │ │ └── SubjectAdapter.kt │ │ ├── say │ │ │ ├── SayActivity.kt │ │ │ ├── SayAdapter.kt │ │ │ ├── SayPresenter.kt │ │ │ └── SayView.kt │ │ ├── search │ │ │ ├── MonoAdapter.kt │ │ │ ├── SearchActivity.kt │ │ │ ├── SearchAdapter.kt │ │ │ ├── SearchHistoryAdapter.kt │ │ │ ├── SearchPresenter.kt │ │ │ └── SearchTypeView.kt │ │ ├── setting │ │ │ └── SettingsActivity.kt │ │ ├── splash │ │ │ └── SplashActivity.kt │ │ ├── subject │ │ │ ├── BlogAdapter.kt │ │ │ ├── CharacterAdapter.kt │ │ │ ├── CommentAdapter.kt │ │ │ ├── EditSubjectDialog.kt │ │ │ ├── EpisodeAdapter.kt │ │ │ ├── EpisodeDialog.kt │ │ │ ├── EpisodeListDialog.kt │ │ │ ├── InfoboxDialog.kt │ │ │ ├── LinkedSubjectAdapter.kt │ │ │ ├── SeasonAdapter.kt │ │ │ ├── SitesAdapter.kt │ │ │ ├── SmallEpisodeAdapter.kt │ │ │ ├── SubjectActivity.kt │ │ │ ├── SubjectPresenter.kt │ │ │ ├── SubjectView.kt │ │ │ ├── TagAdapter.kt │ │ │ └── TopicAdapter.kt │ │ ├── topic │ │ │ ├── EmojiAdapter.kt │ │ │ ├── PhotoPagerAdapter.kt │ │ │ ├── PostAdapter.kt │ │ │ ├── ReplyDialog.kt │ │ │ ├── TopicActivity.kt │ │ │ ├── TopicPresenter.kt │ │ │ └── TopicView.kt │ │ ├── view │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseDialog.kt │ │ │ ├── BaseFragmentActivity.kt │ │ │ ├── BaseNodeAdapter.kt │ │ │ ├── CleanableEditText.kt │ │ │ ├── CollapsibleAppBarHelper.kt │ │ │ ├── DimBlurTransform.kt │ │ │ ├── DragPhotoView.kt │ │ │ ├── DragSelectTouchListener.kt │ │ │ ├── FastScrollPopup.java │ │ │ ├── FastScrollRecyclerView.kt │ │ │ ├── FastScroller.java │ │ │ ├── FitScreenPhotoView.kt │ │ │ ├── FixMultiViewPager.kt │ │ │ ├── FixSwipeRefreshLayout.kt │ │ │ ├── HotViewFlipper.kt │ │ │ ├── NestedWebView.kt │ │ │ ├── NotifyActionProvider.kt │ │ │ ├── NumberPicker.kt │ │ │ ├── OnFastScrollStateChangeListener.java │ │ │ ├── RecyclerTabLayout.kt │ │ │ ├── RoundBackgroundDecoration.kt │ │ │ └── ShadowDecoration.kt │ │ └── web │ │ │ └── WebActivity.kt │ │ └── util │ │ ├── AppUtil.kt │ │ ├── FileRequestBody.kt │ │ ├── FixAppBarLayoutBehavior.java │ │ ├── GlideUtil.kt │ │ ├── HtmlUtil.kt │ │ ├── HttpUtil.kt │ │ ├── JsonUtil.kt │ │ ├── PluginPreference.kt │ │ ├── ProgressAppGlideModule.kt │ │ ├── ResourceUtil.kt │ │ ├── SpanFormatter.kt │ │ ├── TimeUtil.kt │ │ ├── WebViewCookieHandler.java │ │ └── span │ │ ├── BaseLineImageSpan.kt │ │ ├── ClickableImageSpan.kt │ │ ├── ClickableUrlSpan.kt │ │ ├── CodeLineSpan.kt │ │ ├── CollapseUrlDrawable.kt │ │ ├── MaskSpan.kt │ │ ├── QuoteLineSpan.kt │ │ ├── TextViewDrawable.kt │ │ ├── UploadDrawable.kt │ │ └── UrlDrawable.kt │ └── res │ ├── anim │ ├── fade_in.xml │ ├── fade_out.xml │ ├── move_in.xml │ └── move_out.xml │ ├── color │ ├── color_bg_checkable.xml │ ├── color_calendar_selector.xml │ ├── color_checkable.xml │ ├── color_checkable_white.xml │ └── color_selectable.xml │ ├── drawable-xhdpi │ ├── akkarin.jpg │ ├── bangumi_detail_ic_season_first.9.png │ ├── bangumi_detail_ic_season_last.9.png │ ├── bangumi_detail_ic_season_middle.9.png │ ├── empty.webp │ ├── err_401.webp │ ├── err_404.webp │ └── welcome.webp │ ├── drawable │ ├── bg_calendar_selector.xml │ ├── bg_episode_badge.xml │ ├── bg_episode_outline.xml │ ├── bg_notify_badge.xml │ ├── bg_round_checkable.xml │ ├── bg_round_dialog.xml │ ├── bg_round_outline.xml │ ├── bg_round_rect.xml │ ├── bg_say_left.xml │ ├── bg_say_right.xml │ ├── bg_selectable.xml │ ├── bg_splash.xml │ ├── divider.xml │ ├── ic_add.xml │ ├── ic_broken_image.xml │ ├── ic_calendar.xml │ ├── ic_check_circle.xml │ ├── ic_chevron_left.xml │ ├── ic_chevron_right.xml │ ├── ic_clear.xml │ ├── ic_code.xml │ ├── ic_edit.xml │ ├── ic_emoji_keyboard.xml │ ├── ic_exit.xml │ ├── ic_explore.xml │ ├── ic_format.xml │ ├── ic_heart.xml │ ├── ic_heart_outline.xml │ ├── ic_history.xml │ ├── ic_home.xml │ ├── ic_image.xml │ ├── ic_insert_emoji.xml │ ├── ic_keyboard.xml │ ├── ic_lock.xml │ ├── ic_logo.xml │ ├── ic_notifications.xml │ ├── ic_notifications_none.xml │ ├── ic_search.xml │ ├── ic_send.xml │ ├── ic_settings.xml │ ├── ic_time.xml │ ├── ic_timelapse.xml │ ├── ic_widgets.xml │ ├── placeholder.xml │ └── placeholder_round.xml │ ├── layout │ ├── action_notify.xml │ ├── activity_main.xml │ ├── activity_subject.xml │ ├── activity_topic.xml │ ├── activity_web.xml │ ├── appbar_layout.xml │ ├── base_activity.xml │ ├── base_dialog.xml │ ├── brvah_quick_view_load_more.xml │ ├── content_calendar.xml │ ├── content_history.xml │ ├── content_home.xml │ ├── content_index.xml │ ├── dialog_edit_subject.xml │ ├── dialog_episode_list.xml │ ├── dialog_epsode.xml │ ├── dialog_infobox.xml │ ├── dialog_reply.xml │ ├── dialog_subject.xml │ ├── fragment_collection.xml │ ├── fragment_rakuen.xml │ ├── fragment_search.xml │ ├── fragment_timeline.xml │ ├── item_avatar_header.xml │ ├── item_blog.xml │ ├── item_calendar.xml │ ├── item_calendar_now.xml │ ├── item_calendar_tab.xml │ ├── item_character.xml │ ├── item_comment.xml │ ├── item_emoji.xml │ ├── item_episode.xml │ ├── item_episode_flipper.xml │ ├── item_episode_header.xml │ ├── item_episode_small.xml │ ├── item_mono.xml │ ├── item_rakuen_tab.xml │ ├── item_reply.xml │ ├── item_say.xml │ ├── item_search_history.xml │ ├── item_season.xml │ ├── item_site.xml │ ├── item_subject.xml │ ├── item_subject_small.xml │ ├── item_subject_topic.xml │ ├── item_tag.xml │ ├── item_timeline.xml │ ├── item_topic.xml │ ├── nav_header.xml │ ├── number_picker.xml │ ├── pref_coffee_header.xml │ ├── pref_plugin_widget.xml │ ├── subject_detail.xml │ ├── view_empty.xml │ └── view_login.xml │ ├── menu │ ├── action_main.xml │ ├── action_subject.xml │ ├── action_web.xml │ ├── drawer_main.xml │ ├── list_browser_type.xml │ ├── list_format.xml │ ├── list_notify.xml │ ├── list_search_type.xml │ ├── list_timeline.xml │ ├── list_topic_filter.xml │ └── nav_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-night │ └── styles.xml │ ├── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── app_preferences.xml │ ├── filepaths.xml │ └── network_security_config.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── prev1.jpg ├── prev2.jpg ├── prev3.jpg └── prev4.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bangumi 2 | [![GitHub release](https://img.shields.io/github/release/ekibun/Bangumi.svg)](https://github.com/ekibun/Bangumi/releases) 3 | [![GitHub license](https://img.shields.io/github/license/ekibun/Bangumi.svg)](https://github.com/ekibun/Bangumi) 4 | [![GitHub download](https://img.shields.io/github/downloads/ekibun/Bangumi/total.svg)](https://github.com/ekibun/Bangumi/releases) 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b88b68a9b500403f9062a86a8c3388c0)](https://www.codacy.com/manual/ekibun/Bangumi?utm_source=github.com&utm_medium=referral&utm_content=ekibun/Bangumi&utm_campaign=Badge_Grade) 6 | 7 | 这是一个参(抄)考(袭)了bilibili和隔壁各个bgm客户端的又双叒叕一个[Bangumi番组计划](https://bgm.tv)Android客户端 8 | 9 | ## 这东西有啥用 10 | 11 | - 更原生的收藏列表 12 | 13 | - 更详细的放送时间表 14 | 15 | - 更好用的番组搜索/索引 16 | 17 | - 更全面的番组详情 18 | 19 | - 更快捷的收藏/进度管理 20 | 21 | - 原生体验的超展开/时间胶囊 22 | 23 | ## 这东西长啥样 24 | 25 | | ![](./images/prev1.jpg) | ![](./images/prev4.jpg) | ![](./images/prev3.jpg) | ![](./images/prev2.jpg) | 26 | | ---- | ---- | ---- | ---- | 27 | 28 | ## 感谢以下的开源项目及作者 29 | 30 | - [Gson](https://github.com/google/gson) 31 | 32 | - [Jsoup](https://jsoup.org/) 33 | 34 | - [Glide](https://github.com/bumptech/glide) 35 | 36 | - [GlideWebpDecoder](https://github.com/zjupure/GlideWebpDecoder) 37 | 38 | - [glide-transformations](https://github.com/wasabeef/glide-transformations) 39 | 40 | - [okhttp](https://github.com/square/okhttp) 41 | 42 | - [retrofit](https://github.com/square/retrofit) 43 | 44 | - [base-adapter](https://github.com/hongyangAndroid/baseAdapter) 45 | 46 | - [BaseRecyclerViewAdapterHelper](https://github.com/CymChad/BaseRecyclerViewAdapterHelper) 47 | 48 | - [StickyItemDecoration](https://github.com/oubowu/Stickyitemdecoration) 49 | 50 | - [android-shape-imageview](https://github.com/siyamed/android-shape-imageview) 51 | 52 | - [RecyclerTabLayout](https://github.com/nshmura/RecyclerTabLayout) 53 | 54 | - [RecyclePagerAdapter](https://github.com/AlexMofer/RecyclePagerAdapter) 55 | 56 | - [DiskLruCache](https://github.com/JakeWharton/DiskLruCache) 57 | 58 | - [PhotoView](https://github.com/chrisbanes/PhotoView) 59 | 60 | - [FastScrollRecyclerView](https://github.com/timusus/RecyclerView-FastScroll) 61 | 62 | - [RichEditText](https://github.com/awarmisland/RichEditText) 63 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in buildCallback.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontobfuscate 24 | -keep class soko.ekibun.bangumi.**{*;} -------------------------------------------------------------------------------- /app/sampledata/episode.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "type": 0, 5 | "header": "本篇", 6 | "sort": "第 1 话" 7 | }, 8 | { 9 | "type": 0, 10 | "header": " ", 11 | "sort": "第 1 话" 12 | }, 13 | { 14 | "type": 1, 15 | "header": " ", 16 | "sort": "第 1 话" 17 | }, 18 | { 19 | "type": 1, 20 | "header": " ", 21 | "sort": "第 2 话" 22 | }, 23 | { 24 | "type": 1, 25 | "header": " ", 26 | "sort": "第 3 话" 27 | }, 28 | { 29 | "type": 1, 30 | "header": " ", 31 | "sort": "第 4 话" 32 | }, 33 | { 34 | "type": 1, 35 | "header": " ", 36 | "sort": "第 5 话" 37 | }, 38 | { 39 | "type": 0, 40 | "header": " ", 41 | "sort": "第 6 话" 42 | }, 43 | { 44 | "type": 0, 45 | "header": "特别篇", 46 | "sort": "第 5 话" 47 | }, 48 | { 49 | "type": 0, 50 | "header": " ", 51 | "sort": "第 6 话" 52 | }, 53 | { 54 | "type": 1, 55 | "header": " ", 56 | "sort": "特别篇 1" 57 | }, 58 | { 59 | "type": 1, 60 | "sort": "特别篇 2" 61 | }, 62 | { 63 | "type": 1, 64 | "sort": "特别篇 3" 65 | } 66 | ] 67 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/soko/ekibun/bangumi/SpanTest.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi 2 | 3 | import org.junit.Test 4 | import soko.ekibun.bangumi.util.HtmlUtil 5 | 6 | class SpanTest { 7 | 8 | @Test 9 | fun test() { 10 | val html = """test
11 | Bold 12 | Italic underline remove
13 | mask
14 | red
15 | size
16 | url
17 |
18 |
  • list item
    19 | line break
    20 |
  • list item
    quote
    21 |
    new line
    22 | 1
    23 | 2
    24 | 3
    25 | 4
    26 |   code
    27 |     <a></a>
    """ 28 | val span = HtmlUtil.html2span(html) 29 | println(HtmlUtil.span2bbcode(span)) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/awarmisland/android/richedittext/bean/FontStyle.java: -------------------------------------------------------------------------------- 1 | package com.awarmisland.android.richedittext.bean; 2 | 3 | /** 4 | * 字体样式 5 | */ 6 | public class FontStyle { 7 | public boolean isBold; 8 | public boolean isItalic; 9 | public boolean isUnderline; 10 | public boolean isStrike; 11 | public boolean isMask; 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/awarmisland/android/richedittext/bean/SpanPart.java: -------------------------------------------------------------------------------- 1 | package com.awarmisland.android.richedittext.bean; 2 | 3 | /** 4 | * span 样式记录 5 | */ 6 | public class SpanPart extends FontStyle { 7 | public int start; 8 | public int end; 9 | 10 | public SpanPart(int start, int end) { 11 | this.start = start; 12 | this.end = end; 13 | } 14 | 15 | public SpanPart(FontStyle fontStyle) { 16 | this.isBold = fontStyle.isBold; 17 | this.isItalic = fontStyle.isItalic; 18 | this.isStrike = fontStyle.isStrike; 19 | this.isMask = fontStyle.isMask; 20 | this.isUnderline = fontStyle.isUnderline; 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bumptech/glide/samples/svg/SvgDecoder.java: -------------------------------------------------------------------------------- 1 | package com.bumptech.glide.samples.svg; 2 | 3 | import androidx.annotation.NonNull; 4 | import com.bumptech.glide.load.Options; 5 | import com.bumptech.glide.load.ResourceDecoder; 6 | import com.bumptech.glide.load.engine.Resource; 7 | import com.bumptech.glide.load.resource.SimpleResource; 8 | import com.caverock.androidsvg.SVG; 9 | import com.caverock.androidsvg.SVGParseException; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | 14 | /** 15 | * Decodes an SVG internal representation from an {@link InputStream}. 16 | */ 17 | public class SvgDecoder implements ResourceDecoder { 18 | 19 | @Override 20 | public boolean handles(@NonNull InputStream source, @NonNull Options options) { 21 | // TODO: Can we tell? 22 | return true; 23 | } 24 | 25 | public Resource decode( 26 | @NonNull InputStream source, int width, int height, @NonNull Options options) 27 | throws IOException { 28 | try { 29 | SVG svg = SVG.getFromInputStream(source); 30 | return new SimpleResource<>(svg); 31 | } catch (SVGParseException ex) { 32 | throw new IOException("Cannot load SVG from stream", ex); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bumptech/glide/samples/svg/SvgDrawableTranscoder.java: -------------------------------------------------------------------------------- 1 | package com.bumptech.glide.samples.svg; 2 | 3 | import android.graphics.Picture; 4 | import android.graphics.drawable.PictureDrawable; 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | import com.bumptech.glide.load.Options; 8 | import com.bumptech.glide.load.engine.Resource; 9 | import com.bumptech.glide.load.resource.SimpleResource; 10 | import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; 11 | import com.caverock.androidsvg.SVG; 12 | 13 | /** 14 | * Convert the {@link SVG}'s internal representation to an Android-compatible one ({@link Picture}). 15 | */ 16 | public class SvgDrawableTranscoder implements ResourceTranscoder { 17 | @Nullable 18 | @Override 19 | public Resource transcode( 20 | @NonNull Resource toTranscode, @NonNull Options options) { 21 | SVG svg = toTranscode.get(); 22 | Picture picture = svg.renderToPicture(); 23 | PictureDrawable drawable = new PictureDrawable(picture); 24 | return new SimpleResource<>(drawable); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/oubowu/stickyitemdecoration/FullSpanUtil.java: -------------------------------------------------------------------------------- 1 | package com.oubowu.stickyitemdecoration; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.recyclerview.widget.GridLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 8 | 9 | /** 10 | * Created by Oubowu on 2016/7/29 17:28. 11 | */ 12 | public class FullSpanUtil { 13 | 14 | public static void onAttachedToRecyclerView(RecyclerView recyclerView, final RecyclerView.Adapter adapter, final int pinnedHeaderType) { 15 | // 如果是网格布局,这里处理标签的布局占满一行 16 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 17 | if (layoutManager instanceof GridLayoutManager) { 18 | final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; 19 | final GridLayoutManager.SpanSizeLookup oldSizeLookup = gridLayoutManager.getSpanSizeLookup(); 20 | gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 21 | @Override 22 | public int getSpanSize(int position) { 23 | if (adapter.getItemViewType(position) == pinnedHeaderType) { 24 | return gridLayoutManager.getSpanCount(); 25 | } 26 | if (oldSizeLookup != null) { 27 | return oldSizeLookup.getSpanSize(position); 28 | } 29 | return 1; 30 | } 31 | }); 32 | } 33 | } 34 | 35 | public static void onViewAttachedToWindow(RecyclerView.ViewHolder holder, RecyclerView.Adapter adapter, int pinnedHeaderType) { 36 | // 如果是瀑布流布局,这里处理标签的布局占满一行 37 | final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 38 | if (lp instanceof StaggeredGridLayoutManager.LayoutParams) { 39 | final StaggeredGridLayoutManager.LayoutParams slp = (StaggeredGridLayoutManager.LayoutParams) lp; 40 | slp.setFullSpan(adapter.getItemViewType(holder.getLayoutPosition()) == pinnedHeaderType); 41 | } 42 | } 43 | 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/oubowu/stickyitemdecoration/OnStickyChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.oubowu.stickyitemdecoration; 2 | 3 | public interface OnStickyChangeListener { 4 | void onScrollable(int offset); 5 | 6 | void onInVisible(); 7 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/App.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.preference.PreferenceManager 6 | import com.umeng.commonsdk.UMConfigure 7 | import soko.ekibun.bangumi.model.DataCacheModel 8 | import soko.ekibun.bangumi.model.PluginsModel 9 | import soko.ekibun.bangumi.model.ThemeModel 10 | import soko.ekibun.bangumi.model.UserModel 11 | import soko.ekibun.bangumi.util.HttpUtil 12 | 13 | /** 14 | * App entry 15 | * @property dataCacheModel DataCacheModel 16 | * @property pluginInstance Map? 17 | */ 18 | class App : Application() { 19 | val sp by lazy { PreferenceManager.getDefaultSharedPreferences(this) } 20 | val dataCacheModel by lazy { DataCacheModel(this) } 21 | lateinit var pluginInstance: Map 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | app = this 26 | 27 | HttpUtil.formhash = UserModel.userList.let { it.users[it.current] }?.formhash ?: HttpUtil.formhash 28 | ThemeModel.setTheme(this, ThemeModel.getTheme()) 29 | UMConfigure.init( 30 | this, 31 | "5e68fe80167edd6e34000185", 32 | if (BuildConfig.DEBUG) "debug" else BuildConfig.FLAVOR, 33 | UMConfigure.DEVICE_TYPE_PHONE, 34 | null 35 | ) 36 | 37 | pluginInstance = PluginsModel.createPluginInstance(this) 38 | } 39 | 40 | companion object { 41 | lateinit var app: App 42 | 43 | /** 44 | * get from context 45 | * @param context Context 46 | * @return App 47 | */ 48 | fun get(context: Context): App { 49 | return context.applicationContext as App 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/bangumi/bean/Character.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.bangumi.bean 2 | 3 | import soko.ekibun.bangumi.api.bangumi.Bangumi 4 | 5 | /** 6 | * 虚拟人物类 7 | * @property id Int 8 | * @property role_name String? 9 | * @property comment Int 10 | * @property collects Int 11 | * @property actors List? 12 | * @constructor 13 | */ 14 | class Character( 15 | val id: Int = 0, 16 | name: String? = null, 17 | name_cn: String? = null, 18 | image: String? = null, 19 | val role_name: String? = null, 20 | val comment: Int = 0, 21 | val collects: Int = 0, 22 | val actors: List? = null 23 | ) : MonoInfo(name, name_cn, image, "", "${Bangumi.SERVER}/character/$id") -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/bangumi/bean/Comment.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.bangumi.bean 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import org.jsoup.Jsoup 6 | import soko.ekibun.bangumi.api.bangumi.Bangumi 7 | import soko.ekibun.bangumi.util.HttpUtil 8 | 9 | /** 10 | * 吐槽 11 | * @property user UserInfo? 12 | * @property time String? 13 | * @property comment String? 14 | * @property rate Int 15 | * @constructor 16 | */ 17 | data class Comment( 18 | val user: UserInfo? = null, 19 | val time: String? = null, 20 | val comment: String? = null, 21 | val rate: Int = 0 22 | ) { 23 | companion object { 24 | /** 25 | * 吐槽箱 26 | * @param subject Subject 27 | * @param page Int 28 | * @return Call> 29 | */ 30 | suspend fun getSubjectComment( 31 | subject: Subject, 32 | page: Int 33 | ): List { 34 | return withContext(Dispatchers.Default) { 35 | val doc = Jsoup.parse(withContext(Dispatchers.IO) { 36 | HttpUtil.fetch( 37 | "${Bangumi.SERVER}/subject/${subject.id}/comments?page=$page" 38 | ).body?.string() ?: "" 39 | }) 40 | doc.select("#comment_box .item").mapNotNull { 41 | val user = it.selectFirst(".text a") 42 | val username = UserInfo.getUserName(user?.attr("href")) 43 | Comment( 44 | user = UserInfo( 45 | id = username?.toIntOrNull() ?: 0, 46 | username = username, 47 | nickname = user?.text(), 48 | avatar = Bangumi.parseImageUrl(it.selectFirst(".avatarNeue")) 49 | ), 50 | time = it.selectFirst(".grey")?.text()?.replace("@", "")?.trim(), 51 | comment = it.selectFirst("p")?.text(), 52 | rate = Regex("""stars([0-9]*)""").find( 53 | it.selectFirst(".text")?.selectFirst(".starlight")?.outerHtml() 54 | ?: "" 55 | )?.groupValues?.get(1)?.toIntOrNull() ?: 0 56 | ) 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/bangumi/bean/Images.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.bangumi.bean 2 | 3 | import soko.ekibun.bangumi.App 4 | 5 | /** 6 | * bgm.tv图像类 7 | */ 8 | object Images { 9 | fun medium(url: String?): String = (url ?: "") 10 | .replace(Regex("/r/[\\dx]+/"), "/") 11 | .replace(Regex("/[lcmgs]/"), "/m/") 12 | fun large(url: String?): String = medium(url).replace("/m/", "/l/") 13 | fun common(url: String?): String = medium(url).let { medium -> 14 | if (medium.contains(Regex("/(user|crt|icon)/"))) medium 15 | else medium.replace("/m/", "/c/") 16 | } 17 | 18 | fun small(url: String?): String = medium(url).replace("/m/", "/s/") 19 | fun grid(url: String?): String = medium(url).let { medium -> 20 | if (medium.contains(Regex("/(user|icon)/"))) medium 21 | else medium.replace("/m/", "/g/") 22 | } 23 | 24 | /** 25 | * 返回设置的图像清晰度 26 | * @param url String? 27 | * @param context Context 28 | * @return String 29 | */ 30 | fun getImage(url: String?): String { 31 | return when (App.app.sp.getString("image_quality", "c")) { 32 | "l" -> large(url) 33 | "m" -> medium(url) 34 | "s" -> small(url) 35 | "g" -> grid(url) 36 | else -> common(url) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/bangumi/bean/MonoInfo.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.bangumi.bean 2 | 3 | /** 4 | * 人物信息 5 | * @property name String? 6 | * @property name_cn String? 7 | * @property image String? 8 | * @property summary String? 9 | * @property url String? 10 | * @constructor 11 | */ 12 | open class MonoInfo( 13 | val name: String? = null, 14 | val name_cn: String? = null, 15 | val image: String? = null, 16 | val summary: String? = null, 17 | val url: String? = null 18 | ) -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/bangumi/bean/Person.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.bangumi.bean 2 | 3 | import soko.ekibun.bangumi.api.bangumi.Bangumi 4 | 5 | /** 6 | * 现实人物 7 | * @property id Int 8 | * @constructor 9 | */ 10 | class Person( 11 | val id: Int = 0, 12 | name: String? = null, 13 | name_cn: String? = null, 14 | image: String? = null 15 | ) : MonoInfo(name, name_cn, image, "", "${Bangumi.SERVER}/person/$id") -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/bangumi/bean/UserInfo.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.bangumi.bean 2 | 3 | import org.jsoup.nodes.Element 4 | import soko.ekibun.bangumi.api.bangumi.Bangumi 5 | 6 | /** 7 | * 用户信息类 8 | * @property username String? 9 | * @property nickname String? 10 | * @property avatar String? 11 | * @property sign String? 12 | * @property url String 13 | * @constructor 14 | */ 15 | data class UserInfo( 16 | var id: Int = 0, 17 | var username: String? = null, 18 | var nickname: String? = null, 19 | var avatar: String? = null, 20 | var sign: String? = null 21 | ) { 22 | val url = "${Bangumi.SERVER}/user/$username" 23 | 24 | val name get() = if (nickname.isNullOrEmpty()) username else nickname 25 | 26 | companion object { 27 | /** 28 | * 从url中提取用户名 29 | * @param href String? 30 | * @return String? 31 | */ 32 | fun getUserName(href: String?): String? { 33 | return Regex("""/user/([^/]*)""").find(href ?: "")?.groupValues?.get(1) 34 | } 35 | 36 | /** 37 | * 获取用户信息 38 | * @param user Element? 39 | * @param avatar String? 40 | * @return UserInfo 41 | */ 42 | fun parse(user: Element?, avatar: String? = null): UserInfo { 43 | val username = getUserName(user?.attr("href")) 44 | val userId = username?.toIntOrNull() 45 | return UserInfo( 46 | id = username?.toIntOrNull() ?: 0, 47 | username = username, 48 | nickname = user?.text(), 49 | avatar = avatar ?: userId?.let { 50 | String.format( 51 | "https://lain.bgm.tv/pic/user/l/%03d/%02d/%02d/%d.jpg", 52 | it / 1000000, it / 10000 % 100, it / 100 % 100, it 53 | ) 54 | } 55 | ) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/github/bean/BangumiLinkMap.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.github.bean 2 | 3 | data class BangumiLinkMap( 4 | val id: Int, 5 | val node: List?, 6 | val relate: List? 7 | ) { 8 | data class BangumiLinkSubject( 9 | val id: Int, 10 | val name: String?, 11 | val nameCN: String?, 12 | val image: String?, 13 | var visit: Boolean? = null 14 | ) 15 | 16 | data class BangumiLinkRelate( 17 | val relate: String, 18 | val src: Int, 19 | val dst: Int, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/github/bean/Release.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.github.bean 2 | 3 | /** 4 | * 版本信息 5 | * @property tag_name String? 6 | * @property assets List? 7 | * @property body String? 8 | * @constructor 9 | */ 10 | data class Release( 11 | val tag_name: String? = null, 12 | val assets: List? = null, 13 | val body: String? = null 14 | ){ 15 | /** 16 | * 版本附件信息 17 | * @property name String? 18 | * @property download_count String? 19 | * @property browser_download_url String? 20 | * @constructor 21 | */ 22 | data class Assets( 23 | val name: String? = null, 24 | val download_count: String? = null, 25 | val browser_download_url: String? = null 26 | ) 27 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/sda1/Sda1.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.sda1 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import okhttp3.RequestBody 6 | import soko.ekibun.bangumi.api.sda1.bean.Response 7 | import soko.ekibun.bangumi.util.HttpUtil 8 | import soko.ekibun.bangumi.util.JsonUtil 9 | 10 | /** 11 | * 流浪图库 12 | */ 13 | object Sda1 { 14 | 15 | /** 16 | * 上传图片 17 | * @param requestBody RequestBody 18 | * @param fileName String 19 | * @return Call 20 | */ 21 | suspend fun uploadImage(requestBody: RequestBody, fileName: String): String { 22 | return withContext(Dispatchers.IO) { 23 | JsonUtil.toEntity( 24 | HttpUtil.fetch( 25 | "https://p.sda1.dev/api/v1/upload_external_noform?fileName=$fileName", 26 | HttpUtil.RequestOption(body = requestBody) 27 | ).body?.string() ?: "" 28 | )?.data?.url ?: "" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/api/sda1/bean/Response.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.api.sda1.bean 2 | 3 | /** 4 | * 图片上传返回数据 5 | * @property success Boolean 6 | * @property code String? 7 | * @property data DataBean? 8 | * @constructor 9 | */ 10 | data class Response( 11 | var success: Boolean = false, 12 | var code: String? = null, 13 | var data: DataBean? = null 14 | ) { 15 | 16 | /** 17 | * 图片数据 18 | * @property url String? 19 | * @property delete_url String? 20 | * @constructor 21 | */ 22 | data class DataBean( 23 | var url: String? = null, 24 | var delete_url: String? = null 25 | ) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/model/PluginsModel.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.model 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.preference.PreferenceManager 7 | import soko.ekibun.bangumi.App 8 | 9 | /** 10 | * 插件类 11 | */ 12 | object PluginsModel { 13 | /** 14 | * 创建插件实例 15 | * @param context Context 16 | * @return Pair? 17 | */ 18 | fun createPluginInstance(context: Context): Map { 19 | return context.packageManager.queryIntentServices( 20 | Intent("soko.ekibun.bangumi.plugins"), 0 21 | ).distinctBy { it.serviceInfo.packageName }.mapNotNull { 22 | try { 23 | val pluginContext = context.createPackageContext( 24 | it.serviceInfo.packageName, 25 | Context.CONTEXT_IGNORE_SECURITY or Context.CONTEXT_INCLUDE_CODE 26 | ) 27 | val pluginClass = pluginContext.classLoader.loadClass(it.serviceInfo.name) 28 | pluginContext to pluginClass.getDeclaredConstructor().let { 29 | it.isAccessible = true 30 | it.newInstance() 31 | } 32 | } catch (e: Exception) { 33 | e.printStackTrace() 34 | null 35 | } 36 | }.toMap() 37 | } 38 | 39 | /** 40 | * 应用插件 41 | * @param context Context 42 | * @return 43 | */ 44 | fun setUpPlugins(activity: Activity): Int { 45 | val sp = PreferenceManager.getDefaultSharedPreferences(App.app) 46 | if (!sp.getBoolean("plugins_enabled", true)) return 0 47 | return App.app.pluginInstance.filter { 48 | if (!sp.getBoolean("use_plugin_${it.key.packageName}", true)) return@filter false 49 | try { 50 | val method = 51 | it.value.javaClass.getMethod("setUpPlugins", Activity::class.java, Context::class.java) 52 | method.invoke(it.value, activity, it.key) 53 | true 54 | } catch (e: Exception) { 55 | e.printStackTrace() 56 | false 57 | } 58 | }.size 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/model/SearchHistoryModel.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.model 2 | 3 | import soko.ekibun.bangumi.App 4 | import soko.ekibun.bangumi.util.JsonUtil 5 | 6 | /** 7 | * 搜索历史 8 | */ 9 | object SearchHistoryModel { 10 | 11 | /** 12 | * 添加 13 | * @param searchKey String 14 | */ 15 | fun addHistory(searchKey: String) { 16 | val newList = getHistoryList().toMutableList() 17 | newList.add(0, searchKey) 18 | App.app.sp.edit().putString(PREF_SEARCH_HISTORY, JsonUtil.toJson(newList.distinct())).apply() 19 | } 20 | 21 | /** 22 | * 删除 23 | * @param searchKey String 24 | * @return Boolean 25 | */ 26 | fun removeHistory(searchKey: String): Boolean { 27 | val newList = getHistoryList().toMutableList() 28 | val removed = newList.remove(searchKey) 29 | App.app.sp.edit().putString(PREF_SEARCH_HISTORY, JsonUtil.toJson(newList)).apply() 30 | return removed 31 | } 32 | 33 | /** 34 | * 清除 35 | */ 36 | fun clearHistory() { 37 | App.app.sp.edit().putString(PREF_SEARCH_HISTORY, JsonUtil.toJson(ArrayList())).apply() 38 | } 39 | 40 | /** 41 | * 获取列表 42 | * @return List 43 | */ 44 | fun getHistoryList(): List { 45 | return JsonUtil.toEntity>( 46 | App.app.sp.getString(PREF_SEARCH_HISTORY, JsonUtil.toJson(ArrayList())) 47 | ?: "" 48 | ) ?: ArrayList() 49 | } 50 | 51 | const val PREF_SEARCH_HISTORY = "searchHistory" 52 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/model/history/History.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.model.history 2 | 3 | import android.content.Context 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import soko.ekibun.bangumi.ui.say.SayActivity 7 | import soko.ekibun.bangumi.ui.subject.SubjectActivity 8 | import soko.ekibun.bangumi.ui.topic.TopicActivity 9 | import soko.ekibun.bangumi.util.JsonUtil 10 | import soko.ekibun.bangumi.util.TimeUtil 11 | import java.util.* 12 | 13 | @Entity(tableName = "history") 14 | data class History( 15 | @PrimaryKey 16 | val cacheKey: String, 17 | val timestamp: Long, 18 | val type: String, 19 | val thumb: String? = null, 20 | val title: String? = null, 21 | val subTitle: String? = null, 22 | val data: String 23 | ) { 24 | fun startActivity(context: Context) { 25 | when (type) { 26 | "subject" -> SubjectActivity.startActivity(context, JsonUtil.toEntity(data)!!) 27 | "topic" -> TopicActivity.startActivity(context, JsonUtil.toEntity(data)!!) 28 | "say" -> SayActivity.startActivity(context, JsonUtil.toEntity(data)!!) 29 | } 30 | } 31 | 32 | val timeString: String get() = TimeUtil.timeFormat.format(Date(timestamp)) 33 | val dateString: String get() = TimeUtil.dateFormat.format(Date(timestamp)) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/model/history/HistoryDao.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.model.history 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | interface HistoryDao { 7 | 8 | @Query("SELECT * FROM history ORDER BY timestamp DESC LIMIT :limit OFFSET :offset") 9 | suspend fun getListOffset(limit: Int, offset: Int): List 10 | 11 | @Insert(onConflict = OnConflictStrategy.REPLACE) 12 | suspend fun insert(history: History) 13 | 14 | @Insert(onConflict = OnConflictStrategy.REPLACE) 15 | suspend fun insert(history: List) 16 | 17 | @Delete 18 | suspend fun delete(history: History) 19 | 20 | @Query("DELETE FROM history") 21 | suspend fun deleteAll() 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/model/history/HistoryDatabase.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.model.history 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | 6 | @Database(entities = [History::class], version = 1) 7 | abstract class HistoryDatabase : RoomDatabase() { 8 | abstract fun historyDao(): HistoryDao 9 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/action/ActionActivity.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.action 2 | 3 | import soko.ekibun.bangumi.ui.view.BaseActivity 4 | 5 | class ActionActivity : BaseActivity() -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/UserView.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main 2 | 3 | import android.view.View 4 | import com.bumptech.glide.request.RequestOptions 5 | import kotlinx.android.synthetic.main.activity_main.* 6 | import kotlinx.android.synthetic.main.nav_header.view.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.api.bangumi.bean.Images 9 | import soko.ekibun.bangumi.api.bangumi.bean.UserInfo 10 | import soko.ekibun.bangumi.model.UserModel 11 | import soko.ekibun.bangumi.util.GlideUtil 12 | 13 | /** 14 | * 用户信息View 15 | * @property context MainActivity 16 | * @property headerView (android.view.View..android.view.View?) 17 | * @constructor 18 | */ 19 | class UserView(private val context: MainActivity, onUserFigureClickListener: View.OnClickListener) { 20 | val headerView by lazy { context.nav_view.getHeaderView(0) } 21 | 22 | init { 23 | headerView.user_figure.setOnClickListener(onUserFigureClickListener) 24 | setUser(UserModel.current()) 25 | } 26 | 27 | /** 28 | * 更新用户信息 29 | * @param user UserInfo? 30 | */ 31 | fun setUser(user: UserInfo?) { 32 | if (context.isDestroyed) return 33 | GlideUtil.with(headerView.user_figure) 34 | ?.load(Images.large(user?.avatar)) 35 | ?.apply( 36 | RequestOptions.circleCropTransform().error(R.drawable.akkarin).placeholder(R.drawable.placeholder_round) 37 | ) 38 | ?.into(headerView.user_figure) 39 | headerView.user_id.text = if (user?.username == null) "" else "@${user.username}" 40 | headerView.user_name.text = user?.nickname ?: context.getString(R.string.hint_login) 41 | headerView.user_sign.visibility = if (user?.sign.isNullOrEmpty()) View.GONE else View.VISIBLE 42 | headerView.user_sign.text = user?.sign 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/DrawerFragment.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.Menu 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.annotation.LayoutRes 9 | 10 | /** 11 | * 抽屉fragment 12 | * @property resId Int 13 | * @property titleRes Int 14 | * @constructor 15 | */ 16 | abstract class DrawerFragment(@LayoutRes private val resId: Int): androidx.fragment.app.Fragment(){ 17 | abstract val titleRes: Int 18 | 19 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 20 | return inflater.inflate(resId, container, false) 21 | } 22 | 23 | /** 24 | * 返回处理 25 | * @return Boolean 26 | */ 27 | open fun processBack(): Boolean { 28 | return false 29 | } 30 | 31 | open fun onCreateOptionsMenu(menu: Menu) {} 32 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/calendar/CalendarFragment.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.calendar 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.View 6 | import kotlinx.android.synthetic.main.content_calendar.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.ui.main.MainActivity 9 | import soko.ekibun.bangumi.ui.main.fragment.DrawerFragment 10 | 11 | /** 12 | * 时间表 13 | * @property titleRes Int 14 | */ 15 | class CalendarFragment: DrawerFragment(R.layout.content_calendar) { 16 | override val titleRes: Int = R.string.calendar 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | 21 | val adapter = CalendarPagerAdapter(root_layout) 22 | item_pager?.adapter = adapter 23 | item_pager?.currentItem = savedInstanceState?.getInt("index_select_index") ?: 7 24 | item_tabs?.setUpWithAdapter(CalendarTabAdapter(item_pager)) 25 | 26 | root_layout?.setOnApplyWindowInsetsListener { _, insets -> 27 | adapter.windowInsets = insets 28 | insets 29 | } 30 | } 31 | 32 | override fun onPrepareOptionsMenu(menu: Menu) { 33 | menu.findItem(R.id.action_search)?.isVisible = true 34 | super.onPrepareOptionsMenu(menu) 35 | } 36 | 37 | override fun processBack(): Boolean { 38 | if (item_pager == null || item_pager?.currentItem == 7) return false 39 | item_pager?.currentItem = 7 40 | return true 41 | } 42 | 43 | override fun onSaveInstanceState(outState: Bundle) { 44 | super.onSaveInstanceState(outState) 45 | item_pager?.currentItem?.let { outState.putInt("calendar_select_index", it) } 46 | } 47 | 48 | override fun onViewStateRestored(savedInstanceState: Bundle?) { 49 | super.onViewStateRestored(savedInstanceState) 50 | savedInstanceState?.getInt("calendar_select_index")?.let { 51 | item_pager?.currentItem = it 52 | } 53 | } 54 | 55 | /** 56 | * 用户收藏改变 57 | */ 58 | fun onCollectionChange() { 59 | (activity as? MainActivity)?.mainPresenter?.calendar?.let { 60 | (item_pager?.adapter as? CalendarPagerAdapter)?.setOnAirList(it) 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/calendar/CalendarTabAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.calendar 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.RecyclerView 8 | import androidx.viewpager.widget.ViewPager 9 | import com.nshmura.recyclertablayout.RecyclerTabLayout 10 | import kotlinx.android.synthetic.main.item_calendar_tab.view.* 11 | import soko.ekibun.bangumi.R 12 | import soko.ekibun.bangumi.util.TimeUtil 13 | 14 | /** 15 | * 时间表TabAdapter 16 | * @property viewpager ViewPager 17 | * @constructor 18 | */ 19 | class CalendarTabAdapter(val viewpager: ViewPager) : RecyclerTabLayout.Adapter(viewpager) { 20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 21 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_tab, parent, false) 22 | return ViewHolder(view) 23 | } 24 | 25 | override fun getItemCount(): Int { 26 | return viewPager.adapter?.count?:0 27 | } 28 | 29 | @SuppressLint("SetTextI18n") 30 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 31 | val posDate = (viewpager.adapter as? CalendarPagerAdapter)?.getPostDate(position)?:return 32 | val date = CalendarAdapter.getCalendarInt(posDate) 33 | holder.itemView.item_today.visibility = if(position == 7) View.VISIBLE else View.INVISIBLE 34 | holder.itemView.item_date.text = "${date / 100 % 100}-${date % 100}" 35 | holder.itemView.item_week.text = TimeUtil.weekSmall[CalendarAdapter.getWeek(posDate)] 36 | holder.itemView.item_week.isSelected = currentIndicatorPosition == position 37 | holder.itemView.item_date.isSelected = holder.itemView.item_week.isSelected 38 | holder.itemView.item_today.isSelected = holder.itemView.item_week.isSelected 39 | holder.itemView.item_week.isEnabled = position != 7 40 | } 41 | 42 | /** 43 | * 时间表Tab项 44 | * @constructor 45 | */ 46 | inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 47 | init { 48 | itemView.setOnClickListener { 49 | val pos = adapterPosition 50 | if (pos != RecyclerView.NO_POSITION) { 51 | viewpager.setCurrentItem(pos, true) 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/home/fragment/HomeTabFragment.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.home.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.Menu 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.annotation.LayoutRes 9 | 10 | /** 11 | * 主页fragment 12 | * @property resId Int 13 | * @property titleRes Int 14 | * @property iconRes Int 15 | * @property savedInstanceState Bundle? 16 | * @constructor 17 | */ 18 | abstract class HomeTabFragment(@LayoutRes private val resId: Int): androidx.fragment.app.Fragment(){ 19 | abstract val titleRes: Int 20 | abstract val iconRes: Int 21 | 22 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 23 | return inflater.inflate(resId, container, false) 24 | } 25 | 26 | /** 27 | * 选中 28 | */ 29 | abstract fun onSelect() 30 | 31 | abstract fun onUserChange() 32 | 33 | open fun onCreateOptionsMenu(menu: Menu) {} 34 | 35 | var savedInstanceState: Bundle? = null 36 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/home/fragment/collection/CollectTypeView.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.home.fragment.collection 2 | 3 | import android.widget.TextView 4 | import androidx.appcompat.widget.PopupMenu 5 | import soko.ekibun.bangumi.R 6 | import soko.ekibun.bangumi.api.bangumi.bean.Collection 7 | import soko.ekibun.bangumi.util.ResourceUtil 8 | 9 | /** 10 | * 收藏类型 11 | * @property selectedType Int 12 | * @constructor 13 | */ 14 | class CollectTypeView(private val view: TextView, private val onChange: () -> Unit) { 15 | var selectedType = Collection.statusArray.indexOf(Collection.STATUS_DO) 16 | set(value) { 17 | field = value 18 | val stringArray = view.context.resources.getStringArray(R.array.collection_status_anime) 19 | view.text = stringArray[value] 20 | onChange() 21 | } 22 | 23 | /** 24 | * 获取当前收藏类型 25 | * @return String 26 | */ 27 | fun getType(): String { 28 | return Collection.statusArray[selectedType] 29 | } 30 | 31 | init { 32 | val popup = PopupMenu(view.context, view) 33 | val stringArray = view.context.resources.getStringArray(R.array.collection_status_anime) 34 | stringArray.forEach { 35 | popup.menu.add(it) 36 | } 37 | view.text = stringArray[selectedType] 38 | view.setOnClickListener { 39 | ResourceUtil.checkMenu(view.context, popup.menu) { 40 | stringArray.indexOf(it.title.toString()) == selectedType 41 | } 42 | popup.setOnMenuItemClickListener { 43 | selectedType = stringArray.indexOf(it.title.toString()) 44 | true 45 | } 46 | popup.show() 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/home/fragment/rakuen/RakuenAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.home.fragment.rakuen 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.View 5 | import com.bumptech.glide.request.RequestOptions 6 | import com.chad.library.adapter.base.BaseQuickAdapter 7 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 8 | import kotlinx.android.synthetic.main.item_topic.view.* 9 | import soko.ekibun.bangumi.R 10 | import soko.ekibun.bangumi.api.bangumi.bean.Images 11 | import soko.ekibun.bangumi.api.bangumi.bean.Topic 12 | import soko.ekibun.bangumi.util.GlideUtil 13 | 14 | /** 15 | * 超展开列表Adapter 16 | * @constructor 17 | */ 18 | class RakuenAdapter(data: MutableList? = null) : 19 | BaseQuickAdapter(R.layout.item_topic, data) { 20 | 21 | @SuppressLint("SetTextI18n") 22 | override fun convert(holder: BaseViewHolder, item: Topic) { 23 | holder.itemView.item_title.text = item.title 24 | holder.itemView.item_time.text = "${item.time} (+${item.replyCount})" 25 | holder.itemView.item_group.visibility = View.GONE 26 | item.links?.entries?.firstOrNull()?.let { 27 | holder.itemView.item_group.visibility = View.VISIBLE 28 | holder.itemView.item_group.text = it.key 29 | } 30 | GlideUtil.with(holder.itemView.item_avatar) 31 | ?.load(Images.small(item.image)) 32 | ?.apply( 33 | RequestOptions.circleCropTransform().error(R.drawable.err_404).placeholder(R.drawable.placeholder_round) 34 | ) 35 | ?.into(holder.itemView.item_avatar) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/index/IndexFragment.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.index 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import kotlinx.android.synthetic.main.content_index.* 6 | import soko.ekibun.bangumi.R 7 | import soko.ekibun.bangumi.ui.main.fragment.DrawerFragment 8 | import java.util.* 9 | 10 | /** 11 | * 索引 12 | * @property titleRes Int 13 | */ 14 | class IndexFragment: DrawerFragment(R.layout.content_index){ 15 | override val titleRes: Int = R.string.index 16 | 17 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 18 | super.onViewCreated(view, savedInstanceState) 19 | 20 | val adapter = IndexPagerAdapter(this, item_pager) 21 | item_pager?.adapter = adapter 22 | item_pager?.currentItem = savedInstanceState?.getInt("index_select_index") ?: getNowIndex() 23 | item_tabs?.setUpWithViewPager(item_pager) 24 | 25 | root_layout?.setOnApplyWindowInsetsListener { _, insets -> 26 | adapter.windowInsets = insets 27 | insets 28 | } 29 | } 30 | 31 | override fun onSaveInstanceState(outState: Bundle) { 32 | super.onSaveInstanceState(outState) 33 | item_pager?.currentItem?.let { outState.putInt("index_select_index", it) } 34 | } 35 | 36 | override fun onViewStateRestored(savedInstanceState: Bundle?) { 37 | super.onViewStateRestored(savedInstanceState) 38 | savedInstanceState?.getInt("index_select_index")?.let { 39 | item_pager?.currentItem = it 40 | } 41 | } 42 | 43 | override fun processBack(): Boolean { 44 | val index = getNowIndex() 45 | if (item_pager == null || item_pager?.currentItem == index) return false 46 | item_pager?.currentItem = index 47 | return true 48 | } 49 | 50 | private fun getNowIndex(): Int { 51 | val cal = Calendar.getInstance() 52 | val year = cal.get(Calendar.YEAR) 53 | val month = cal.get(Calendar.MONTH) 54 | return (year - 1000) * 12 + month 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/main/fragment/index/SubjectAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.main.fragment.index 2 | 3 | import android.view.View 4 | import com.bumptech.glide.load.resource.bitmap.CenterCrop 5 | import com.bumptech.glide.load.resource.bitmap.RoundedCorners 6 | import com.bumptech.glide.request.RequestOptions 7 | import com.chad.library.adapter.base.BaseQuickAdapter 8 | import com.chad.library.adapter.base.module.LoadMoreModule 9 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 10 | import kotlinx.android.synthetic.main.item_subject.view.* 11 | import soko.ekibun.bangumi.R 12 | import soko.ekibun.bangumi.api.bangumi.bean.Images 13 | import soko.ekibun.bangumi.api.bangumi.bean.Subject 14 | import soko.ekibun.bangumi.util.GlideUtil 15 | 16 | /** 17 | * 索引条目Adapter 18 | * @constructor 19 | */ 20 | class SubjectAdapter(data: MutableList? = null) : 21 | BaseQuickAdapter(R.layout.item_subject, data), LoadMoreModule { 22 | 23 | override fun convert(holder: BaseViewHolder, item: Subject) { 24 | holder.setText(R.id.item_title, item.displayName) 25 | holder.setText(R.id.item_name_jp, item.name) 26 | holder.setText(R.id.item_summary, item.summary) 27 | holder.itemView.item_chase.visibility = if (item.collect != null) View.VISIBLE else View.GONE 28 | GlideUtil.with(holder.itemView.item_cover) 29 | ?.load(Images.getImage(item.image)) 30 | ?.apply( 31 | RequestOptions.errorOf(R.drawable.err_404).placeholder(R.drawable.placeholder) 32 | .transform(CenterCrop(), RoundedCorners(holder.itemView.item_cover.radius)) 33 | ) 34 | ?.into(holder.itemView.item_cover) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/search/MonoAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.search 2 | 3 | import com.bumptech.glide.request.RequestOptions 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.module.LoadMoreModule 6 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 7 | import kotlinx.android.synthetic.main.item_mono.view.* 8 | import soko.ekibun.bangumi.R 9 | import soko.ekibun.bangumi.api.bangumi.bean.Images 10 | import soko.ekibun.bangumi.api.bangumi.bean.MonoInfo 11 | import soko.ekibun.bangumi.util.GlideUtil 12 | 13 | /** 14 | * 人物Adapter 15 | * @constructor 16 | */ 17 | class MonoAdapter(data: MutableList? = null) : 18 | BaseQuickAdapter(R.layout.item_mono, data), LoadMoreModule { 19 | 20 | override fun convert(holder: BaseViewHolder, item: MonoInfo) { 21 | holder.setText(R.id.item_title, if (item.name_cn.isNullOrEmpty()) item.name else item.name_cn) 22 | holder.setText(R.id.item_name_jp, item.name) 23 | holder.setText(R.id.item_summary, item.summary) 24 | GlideUtil.with(holder.itemView.item_cover) 25 | ?.load(Images.grid(item.image)) 26 | ?.apply( 27 | RequestOptions.circleCropTransform().error(R.drawable.err_404).placeholder(R.drawable.placeholder_round) 28 | ) 29 | ?.into(holder.itemView.item_cover) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/search/SearchActivity.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.search 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.view.View 6 | import kotlinx.android.synthetic.main.fragment_search.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.ui.view.BaseFragmentActivity 9 | 10 | /** 11 | * 搜索Activity 12 | */ 13 | class SearchActivity : BaseFragmentActivity(R.layout.fragment_search) { 14 | 15 | override fun onViewCreated(view: View) { 16 | val historyPaddingBottom = search_history.paddingBottom 17 | val listPaddingBottom = search_list.paddingBottom 18 | view.setOnApplyWindowInsetsListener { _, insets -> 19 | search_history.setPadding( 20 | search_history.paddingLeft, 21 | search_history.paddingTop, 22 | search_history.paddingRight, 23 | historyPaddingBottom + insets.systemWindowInsetBottom 24 | ) 25 | search_list.setPadding( 26 | search_list.paddingLeft, 27 | search_list.paddingTop, 28 | search_list.paddingRight, 29 | listPaddingBottom + insets.systemWindowInsetBottom 30 | ) 31 | insets.consumeSystemWindowInsets() 32 | } 33 | 34 | SearchPresenter(this) 35 | } 36 | 37 | companion object{ 38 | /** 39 | * 启动Activity 40 | * @param context Context 41 | */ 42 | fun startActivity(context: Context) { 43 | val intent = Intent(context, SearchActivity::class.java) 44 | context.startActivity(intent) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/search/SearchAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.search 2 | 3 | import android.view.View 4 | import com.bumptech.glide.load.resource.bitmap.CenterCrop 5 | import com.bumptech.glide.load.resource.bitmap.RoundedCorners 6 | import com.bumptech.glide.request.RequestOptions 7 | import com.chad.library.adapter.base.BaseQuickAdapter 8 | import com.chad.library.adapter.base.module.LoadMoreModule 9 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 10 | import kotlinx.android.synthetic.main.item_subject.view.* 11 | import soko.ekibun.bangumi.R 12 | import soko.ekibun.bangumi.api.bangumi.bean.Images 13 | import soko.ekibun.bangumi.api.bangumi.bean.Subject 14 | import soko.ekibun.bangumi.util.GlideUtil 15 | 16 | /** 17 | * 搜索条目Adapter 18 | * @constructor 19 | */ 20 | class SearchAdapter(data: MutableList? = null) : 21 | BaseQuickAdapter(R.layout.item_subject, data), LoadMoreModule { 22 | 23 | override fun convert(holder: BaseViewHolder, item: Subject) { 24 | holder.setText(R.id.item_title, item.displayName) 25 | holder.setText(R.id.item_name_jp, item.name) 26 | holder.setText(R.id.item_summary, holder.itemView.context.getString(Subject.getTypeRes(item.type))) 27 | holder.itemView.item_chase.visibility = if (item.collect != null) View.VISIBLE else View.GONE 28 | GlideUtil.with(holder.itemView.item_cover) 29 | ?.load(Images.getImage(item.image)) 30 | ?.apply( 31 | RequestOptions.errorOf(R.drawable.err_404).placeholder(R.drawable.placeholder) 32 | .transform(CenterCrop(), RoundedCorners(holder.itemView.item_cover.radius)) 33 | ) 34 | ?.into(holder.itemView.item_cover) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/search/SearchHistoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.search 2 | 3 | import com.chad.library.adapter.base.BaseQuickAdapter 4 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 5 | import kotlinx.android.synthetic.main.item_search_history.view.* 6 | import soko.ekibun.bangumi.R 7 | 8 | /** 9 | * 搜索历史Adapter 10 | * @constructor 11 | */ 12 | class SearchHistoryAdapter(data: MutableList? = null) : 13 | BaseQuickAdapter(R.layout.item_search_history, data) { 14 | 15 | override fun convert(holder: BaseViewHolder, item: String) { 16 | holder.setText(R.id.item_search_key, item) 17 | holder.itemView.item_remove_key.setOnClickListener { 18 | setOnItemChildClick(it, holder.layoutPosition) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/search/SearchTypeView.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.search 2 | 3 | import android.widget.TextView 4 | import androidx.appcompat.view.ContextThemeWrapper 5 | import androidx.appcompat.widget.PopupMenu 6 | import soko.ekibun.bangumi.R 7 | import soko.ekibun.bangumi.api.bangumi.bean.Subject 8 | import soko.ekibun.bangumi.util.ResourceUtil 9 | 10 | /** 11 | * 搜索类型 12 | * @property subjectTypeList Map 13 | * @property monoTypeList Map 14 | * @property selectedType Int 15 | * @constructor 16 | */ 17 | class SearchTypeView(view: TextView, onChange:()->Unit){ 18 | val subjectTypeList = mapOf( 19 | R.id.collection_type_all to Subject.TYPE_ANY, 20 | R.id.collection_type_anime to Subject.TYPE_ANIME, 21 | R.id.collection_type_book to Subject.TYPE_BOOK, 22 | R.id.collection_type_game to Subject.TYPE_GAME, 23 | R.id.collection_type_music to Subject.TYPE_MUSIC, 24 | R.id.collection_type_real to Subject.TYPE_REAL) 25 | val monoTypeList = mapOf( 26 | R.id.collection_type_mono to "all", 27 | R.id.collection_type_ctr to "crt", 28 | R.id.collection_type_psn to "prsn" 29 | ) 30 | var selectedType = R.id.collection_type_all 31 | 32 | init{ 33 | val context = ContextThemeWrapper(view.context, R.style.AppTheme_PopupOverlay) 34 | val popup = PopupMenu(context, view) 35 | popup.menuInflater.inflate(R.menu.list_search_type, popup.menu) 36 | view.text = popup.menu.findItem(selectedType)?.title 37 | view.setOnClickListener { 38 | ResourceUtil.checkMenu(view.context, popup.menu) { 39 | selectedType == it.itemId 40 | } 41 | popup.setOnMenuItemClickListener { 42 | selectedType = it.itemId 43 | view.text = it.title 44 | onChange() 45 | true 46 | } 47 | popup.show() 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/splash/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.splash 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AppCompatActivity 7 | import soko.ekibun.bangumi.ui.main.MainActivity 8 | 9 | /** 10 | * 欢迎Activity 11 | */ 12 | class SplashActivity : AppCompatActivity() { 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | MainActivity.startActivity(this) 17 | finish() 18 | } 19 | 20 | companion object { 21 | /** 22 | * 启动 23 | * @param context Context 24 | */ 25 | fun startActivity(context: Context){ 26 | val intent = Intent(context.applicationContext, SplashActivity::class.java) 27 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) 28 | context.startActivity(intent) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/BlogAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.annotation.SuppressLint 4 | import com.bumptech.glide.request.RequestOptions 5 | import com.chad.library.adapter.base.BaseQuickAdapter 6 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 7 | import kotlinx.android.synthetic.main.item_blog.view.* 8 | import soko.ekibun.bangumi.R 9 | import soko.ekibun.bangumi.api.bangumi.bean.Images 10 | import soko.ekibun.bangumi.api.bangumi.bean.Topic 11 | import soko.ekibun.bangumi.util.GlideUtil 12 | 13 | /** 14 | * 日志Adapter 15 | * @constructor 16 | */ 17 | class BlogAdapter(data: MutableList? = null) : 18 | BaseQuickAdapter(R.layout.item_blog, data) { 19 | 20 | override fun convert(holder: BaseViewHolder, item: Topic) { 21 | @SuppressLint("SetTextI18n") 22 | holder.itemView.item_user.text = item.user?.nickname 23 | holder.itemView.item_time.text = item.time 24 | holder.itemView.item_title.text = item.title 25 | holder.itemView.item_summary.text = item.blog?.pst_content 26 | holder.itemView.item_comment.text = holder.itemView.context.getString(R.string.phrase_reply, item.replyCount) 27 | GlideUtil.with(holder.itemView.item_avatar) 28 | ?.load(Images.getImage(item.image)) 29 | ?.apply( 30 | RequestOptions.circleCropTransform().error(R.drawable.err_404).placeholder(R.drawable.placeholder_round) 31 | ) 32 | ?.into(holder.itemView.item_avatar) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/CharacterAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import com.bumptech.glide.request.RequestOptions 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 6 | import kotlinx.android.synthetic.main.item_character.view.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.api.bangumi.bean.Character 9 | import soko.ekibun.bangumi.api.bangumi.bean.Images 10 | import soko.ekibun.bangumi.util.GlideUtil 11 | 12 | /** 13 | * 角色Adapter 14 | * @constructor 15 | */ 16 | class CharacterAdapter(data: MutableList? = null) : 17 | BaseQuickAdapter(R.layout.item_character, data) { 18 | 19 | override fun convert(holder: BaseViewHolder, item: Character) { 20 | holder.setText(R.id.item_name, if (item.name_cn.isNullOrEmpty()) item.name else item.name_cn) 21 | holder.setText( 22 | R.id.item_cv, 23 | if (item.actors?.isNotEmpty() == true) item.actors.map { if (it.name_cn.isNullOrEmpty()) it.name else it.name_cn } 24 | .reduce { acc, s -> "$acc/$s" } else "") 25 | holder.setText(R.id.item_role, item.role_name) 26 | GlideUtil.with(holder.itemView.item_avatar) 27 | ?.load(Images.grid(item.image)) 28 | ?.apply( 29 | RequestOptions.circleCropTransform().error(R.drawable.err_404).placeholder(R.drawable.placeholder_round) 30 | ) 31 | ?.apply(RequestOptions.circleCropTransform()) 32 | ?.into(holder.itemView.item_avatar) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/CommentAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.view.View 4 | import com.bumptech.glide.request.RequestOptions 5 | import com.chad.library.adapter.base.BaseQuickAdapter 6 | import com.chad.library.adapter.base.module.LoadMoreModule 7 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 8 | import kotlinx.android.synthetic.main.item_comment.view.* 9 | import soko.ekibun.bangumi.R 10 | import soko.ekibun.bangumi.api.bangumi.bean.Comment 11 | import soko.ekibun.bangumi.api.bangumi.bean.Images 12 | import soko.ekibun.bangumi.util.GlideUtil 13 | 14 | /** 15 | * 吐槽Adapter 16 | * @constructor 17 | */ 18 | class CommentAdapter(data: MutableList? = null) : 19 | BaseQuickAdapter(R.layout.item_comment, data), LoadMoreModule { 20 | 21 | override fun convert(holder: BaseViewHolder, item: Comment) { 22 | holder.itemView.item_layout.visibility = if (item.user == null) View.GONE else View.VISIBLE 23 | holder.itemView.item_user.text = item.user?.name 24 | holder.itemView.item_time.text = item.time 25 | holder.itemView.item_title.text = item.comment 26 | //helper.itemView.item_rate.visibility = if(item.rate == 0) View.GONE else View.VISIBLE 27 | holder.itemView.item_rate.rating = item.rate * 0.5f 28 | GlideUtil.with(holder.itemView.item_avatar) 29 | ?.load(Images.small(item.user?.avatar)) 30 | ?.apply( 31 | RequestOptions.circleCropTransform().error(R.drawable.err_404).placeholder(R.drawable.placeholder_round) 32 | ) 33 | ?.into(holder.itemView.item_avatar) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/LinkedSubjectAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.view.View 4 | import com.bumptech.glide.load.resource.bitmap.CenterCrop 5 | import com.bumptech.glide.load.resource.bitmap.RoundedCorners 6 | import com.bumptech.glide.request.RequestOptions 7 | import com.chad.library.adapter.base.BaseQuickAdapter 8 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 9 | import kotlinx.android.synthetic.main.item_subject_small.view.* 10 | import soko.ekibun.bangumi.R 11 | import soko.ekibun.bangumi.api.bangumi.bean.Images 12 | import soko.ekibun.bangumi.api.bangumi.bean.Subject 13 | import soko.ekibun.bangumi.util.GlideUtil 14 | 15 | /** 16 | * 关联条目Adapter 17 | * @constructor 18 | */ 19 | class LinkedSubjectAdapter(data: MutableList? = null) : 20 | BaseQuickAdapter(R.layout.item_subject_small, data) { 21 | 22 | override fun convert(holder: BaseViewHolder, item: Subject) { 23 | holder.setText(R.id.item_title, item.displayName) 24 | holder.itemView.item_summary.visibility = if (item.category.isNullOrEmpty()) View.GONE else View.VISIBLE 25 | holder.setText(R.id.item_summary, item.category) 26 | GlideUtil.with(holder.itemView.item_cover) 27 | ?.load(Images.getImage(item.image)) 28 | ?.apply( 29 | RequestOptions.errorOf(R.drawable.err_404).placeholder(R.drawable.placeholder) 30 | .transform(CenterCrop(), RoundedCorners(holder.itemView.item_cover.radius)) 31 | ) 32 | ?.into(holder.itemView.item_cover) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/SeasonAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.content.res.ColorStateList 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 6 | import kotlinx.android.synthetic.main.item_season.view.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.api.bangumi.bean.Subject 9 | import soko.ekibun.bangumi.util.ResourceUtil 10 | 11 | /** 12 | * 季度列表Adapter 13 | * @property currentId Int 14 | * @constructor 15 | */ 16 | class SeasonAdapter(data: MutableList? = null) : 17 | BaseQuickAdapter 18 | (R.layout.item_season, data) { 19 | var currentId = 0 20 | 21 | override fun convert(holder: BaseViewHolder, item: Subject) { 22 | holder.setText(R.id.item_desc, item.displayName) 23 | holder.itemView.item_desc.setBackgroundResource( 24 | when (getItemPosition(item)) { 25 | 0 -> R.drawable.bangumi_detail_ic_season_first 26 | data.size - 1 -> R.drawable.bangumi_detail_ic_season_last 27 | else -> R.drawable.bangumi_detail_ic_season_middle 28 | } 29 | ) 30 | val color = ResourceUtil.resolveColorAttr( 31 | holder.itemView.context, 32 | if (currentId == item.id) R.attr.colorPrimary 33 | else android.R.attr.textColorSecondary 34 | ) 35 | holder.itemView.item_desc.setTextColor(color) 36 | holder.itemView.item_desc.backgroundTintList = ColorStateList.valueOf(color) 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/SitesAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.content.res.ColorStateList 4 | import android.view.View 5 | import com.chad.library.adapter.base.BaseQuickAdapter 6 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 7 | import kotlinx.android.synthetic.main.item_site.view.* 8 | import soko.ekibun.bangumi.R 9 | import soko.ekibun.bangumi.api.github.bean.OnAirInfo 10 | 11 | /** 12 | * 站点Adapter 13 | * @property collapseLabel Boolean 14 | * @constructor 15 | */ 16 | class SitesAdapter(private val collapseLabel: Boolean = false, data: MutableList? = null) : 17 | BaseQuickAdapter(R.layout.item_site, data) { 18 | 19 | override fun convert(holder: BaseViewHolder, item: OnAirInfo.Site) { 20 | holder.itemView.item_site.text = item.site 21 | holder.itemView.item_site_id.text = item.title() 22 | holder.itemView.item_site.backgroundTintList = ColorStateList.valueOf(item.color) 23 | holder.itemView.item_site_id.visibility = 24 | if (collapseLabel && getItemOrNull(holder.adapterPosition + 1)?.title() == item.title()) 25 | View.GONE else View.VISIBLE 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/SmallEpisodeAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.content.res.ColorStateList 4 | import android.view.View 5 | import com.chad.library.adapter.base.BaseQuickAdapter 6 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 7 | import kotlinx.android.synthetic.main.item_episode_small.view.* 8 | import soko.ekibun.bangumi.R 9 | import soko.ekibun.bangumi.api.bangumi.bean.Episode 10 | import soko.ekibun.bangumi.util.ResourceUtil 11 | 12 | /** 13 | * 剧集列表Adapter 14 | * @constructor 15 | */ 16 | class SmallEpisodeAdapter(data: MutableList? = null) : 17 | BaseQuickAdapter 18 | (R.layout.item_episode_small, data) { 19 | 20 | override fun convert(holder: BaseViewHolder, item: Episode) { 21 | holder.setText(R.id.item_title, item.parseSort()) 22 | holder.setText(R.id.item_desc, if (item.name_cn.isNullOrEmpty()) item.name else item.name_cn) 23 | val color = ResourceUtil.resolveColorAttr( 24 | holder.itemView.context, 25 | when (item.progress) { 26 | Episode.PROGRESS_WATCH -> R.attr.colorPrimary 27 | else -> android.R.attr.textColorSecondary 28 | } 29 | ) 30 | holder.itemView.item_title.setTextColor(color) 31 | holder.itemView.item_desc.setTextColor(color) 32 | holder.itemView.item_badge.visibility = if (item.progress in arrayOf( 33 | Episode.PROGRESS_WATCH, 34 | Episode.PROGRESS_DROP, 35 | Episode.PROGRESS_QUEUE 36 | ) 37 | ) View.VISIBLE else View.INVISIBLE 38 | holder.itemView.item_badge.backgroundTintList = ColorStateList.valueOf( 39 | ResourceUtil.resolveColorAttr( 40 | holder.itemView.context, 41 | when (item.progress) { 42 | in listOf(Episode.PROGRESS_WATCH, Episode.PROGRESS_QUEUE) -> R.attr.colorPrimary 43 | else -> android.R.attr.textColorSecondary 44 | } 45 | ) 46 | ) 47 | holder.itemView.item_badge.text = mapOf( 48 | Episode.PROGRESS_WATCH to R.string.episode_status_watch, 49 | Episode.PROGRESS_DROP to R.string.episode_status_drop, 50 | Episode.PROGRESS_QUEUE to R.string.episode_status_wish 51 | )[item.progress ?: ""]?.let { holder.itemView.context.getString(it) } ?: "" 52 | holder.itemView.backgroundTintList = ColorStateList.valueOf(color) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/TagAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.View 5 | import com.chad.library.adapter.base.BaseQuickAdapter 6 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 7 | import kotlinx.android.synthetic.main.item_tag.view.* 8 | import soko.ekibun.bangumi.R 9 | 10 | /** 11 | * 标签Adapter 12 | * @property hasTag Function1 13 | * @constructor 14 | */ 15 | class TagAdapter(data: MutableList>? = null, var hasTag: (String) -> Boolean = { false }) : 16 | BaseQuickAdapter, BaseViewHolder>(R.layout.item_tag, data) { 17 | 18 | @SuppressLint("SetTextI18n") 19 | override fun convert(holder: BaseViewHolder, item: Pair) { 20 | holder.itemView.item_tag_del.visibility = View.GONE 21 | holder.itemView.item_tag_name.text = item.first 22 | holder.itemView.isSelected = hasTag(item.first) 23 | holder.itemView.item_tag_count.visibility = if (item.second > 0) View.VISIBLE else View.GONE 24 | holder.itemView.item_tag_count.text = "+${item.second}" 25 | } 26 | 27 | override fun setNewInstance(data: MutableList>?) { 28 | super.setNewInstance(data?.sortedByDescending { 29 | hasTag(it.first) 30 | }?.toMutableList()) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/subject/TopicAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.subject 2 | 3 | import android.annotation.SuppressLint 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 6 | import kotlinx.android.synthetic.main.item_subject_topic.view.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.api.bangumi.bean.Topic 9 | 10 | /** 11 | * 讨论板Adapter 12 | * @constructor 13 | */ 14 | class TopicAdapter(data: MutableList? = null) : 15 | BaseQuickAdapter(R.layout.item_subject_topic, data) { 16 | 17 | override fun convert(holder: BaseViewHolder, item: Topic) { 18 | @SuppressLint("SetTextI18n") 19 | holder.itemView.item_user.text = item.user?.nickname 20 | holder.itemView.item_time.text = item.time 21 | holder.itemView.item_title.text = item.title 22 | holder.itemView.item_comment.text = holder.itemView.context.getString(R.string.phrase_reply, item.replyCount) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/topic/EmojiAdapter.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.topic 2 | 3 | import android.annotation.SuppressLint 4 | import com.chad.library.adapter.base.BaseQuickAdapter 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 6 | import kotlinx.android.synthetic.main.item_emoji.view.* 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.util.GlideUtil 9 | 10 | /** 11 | * 表情Adapter 12 | * @constructor 13 | */ 14 | class EmojiAdapter(data: MutableList>? = null) : 15 | BaseQuickAdapter, BaseViewHolder>(R.layout.item_emoji, data) { 16 | 17 | @SuppressLint("SetTextI18n") 18 | override fun convert(holder: BaseViewHolder, item: Pair) { 19 | holder.itemView.item_emoji.setOnClickListener { 20 | setOnItemChildClick(it, holder.layoutPosition) 21 | } 22 | GlideUtil.with(holder.itemView.item_emoji) 23 | ?.load(item.second) 24 | ?.into(holder.itemView.item_emoji) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/BaseDialog.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.view.WindowManager 10 | import android.view.inputmethod.InputMethodManager 11 | import androidx.annotation.LayoutRes 12 | import androidx.fragment.app.DialogFragment 13 | import kotlinx.android.synthetic.main.base_dialog.view.* 14 | import soko.ekibun.bangumi.R 15 | import soko.ekibun.bangumi.model.ThemeModel 16 | 17 | /** 18 | * 基础对话框 19 | * @property resId Int 20 | * @property contentView View? 21 | * @property title String 22 | * @constructor 23 | */ 24 | abstract class BaseDialog(@LayoutRes private val resId: Int) : 25 | DialogFragment() { 26 | var contentView: View? = null 27 | abstract val title: String 28 | abstract fun onViewCreated(view: View) 29 | @SuppressLint("InflateParams") 30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 31 | super.onCreate(savedInstanceState) 32 | val view = contentView ?: inflater.inflate(resId, null) 33 | contentView = view 34 | 35 | view.item_outside?.setOnClickListener { 36 | if (!(it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) 37 | .hideSoftInputFromWindow(view.windowToken, 0) 38 | ) dismiss() 39 | } 40 | view.btn_dismiss?.setOnClickListener { 41 | dismiss() 42 | } 43 | view.dialog_title?.text = title 44 | 45 | onViewCreated(view) 46 | 47 | dialog?.window?.let { ThemeModel.updateNavigationTheme(it, it.context) } 48 | 49 | dialog?.window?.attributes?.let { 50 | it.dimAmount = 0.6f 51 | dialog?.window?.attributes = it 52 | } 53 | dialog?.window?.setWindowAnimations(R.style.AnimDialog) 54 | dialog?.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) 55 | dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) 56 | return view 57 | } 58 | 59 | override fun onCreate(savedInstanceState: Bundle?) { 60 | super.onCreate(savedInstanceState) 61 | setStyle(STYLE_NORMAL, R.style.AppTheme_Dialog) 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/BaseFragmentActivity.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import androidx.annotation.LayoutRes 7 | import com.google.android.material.appbar.AppBarLayout 8 | import kotlinx.android.synthetic.main.appbar_layout.* 9 | import kotlinx.android.synthetic.main.base_activity.* 10 | import soko.ekibun.bangumi.R 11 | 12 | /** 13 | * 基础Activity 14 | * @property resId Int? 15 | * @property collapsibleAppBarHelper CollapsibleAppBarHelper 16 | * @constructor 17 | */ 18 | abstract class BaseFragmentActivity(@LayoutRes private val resId: Int? = null) : 19 | BaseActivity(R.layout.base_activity) { 20 | val collapsibleAppBarHelper by lazy { CollapsibleAppBarHelper(app_bar as AppBarLayout) } 21 | abstract fun onViewCreated(view: View) 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | 26 | val view = if (resId != null) LayoutInflater.from(this).inflate(resId, layout_content) else layout_content 27 | onViewCreated(view) 28 | 29 | setSupportActionBar(toolbar) 30 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 31 | title = title 32 | 33 | root_layout.setOnApplyWindowInsetsListener { _, insets -> 34 | view.dispatchApplyWindowInsets( 35 | insets.replaceSystemWindowInsets( 36 | insets.systemWindowInsetLeft, 0, 37 | insets.systemWindowInsetRight, insets.systemWindowInsetBottom 38 | ) 39 | ) 40 | insets 41 | } 42 | } 43 | 44 | override fun setTitle(title: CharSequence?) { 45 | collapsibleAppBarHelper.setTitle(title.toString(), supportActionBar?.subtitle?.toString()) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/DimBlurTransform.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import android.graphics.Paint 7 | import android.graphics.Rect 8 | import com.bumptech.glide.load.Key 9 | import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool 10 | import jp.wasabeef.glide.transformations.BitmapTransformation 11 | import jp.wasabeef.glide.transformations.internal.FastBlur 12 | import java.security.MessageDigest 13 | 14 | class DimBlurTransform(private val radius: Int, private val sampling: Int, private val dim: Int) : 15 | BitmapTransformation() { 16 | companion object { 17 | private val ID = DimBlurTransform::class.java.name 18 | } 19 | 20 | override fun equals(other: Any?): Boolean { 21 | return other is DimBlurTransform && other.radius == radius && other.sampling == sampling 22 | } 23 | 24 | override fun hashCode(): Int { 25 | return ID.hashCode() + dim + radius * 1000 + sampling * 10 26 | } 27 | 28 | override fun updateDiskCacheKey(messageDigest: MessageDigest) { 29 | messageDigest.update((ID + radius + sampling + dim).toByteArray(Key.CHARSET)) 30 | } 31 | 32 | override fun transform( 33 | context: Context, pool: BitmapPool, 34 | toTransform: Bitmap, outWidth: Int, outHeight: Int 35 | ): Bitmap? { 36 | val width = toTransform.width 37 | val height = toTransform.height 38 | val scaledWidth = width / sampling 39 | val scaledHeight = height / sampling 40 | var bitmap: Bitmap = pool[scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888] 41 | val canvas = Canvas(bitmap) 42 | canvas.scale(1 / sampling.toFloat(), 1 / sampling.toFloat()) 43 | val paint = Paint() 44 | paint.flags = Paint.FILTER_BITMAP_FLAG 45 | canvas.drawBitmap(toTransform, 0f, 0f, paint) 46 | paint.alpha = dim 47 | canvas.drawRect(Rect(0, 0, width, height), paint) 48 | bitmap = FastBlur.blur(bitmap, radius, true) 49 | return bitmap 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/FitScreenPhotoView.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package soko.ekibun.bangumi.ui.view 4 | 5 | import android.content.Context 6 | import android.graphics.drawable.Animatable 7 | import android.graphics.drawable.Drawable 8 | import android.util.AttributeSet 9 | import com.github.chrisbanes.photoview.PhotoView 10 | 11 | /** 12 | * 适应屏幕PhotoView 13 | * @property isMinScale Boolean 14 | * @constructor 15 | */ 16 | open class FitScreenPhotoView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = 0) : PhotoView(context, attr, defStyle) { 17 | val isMinScale get() = scale == 1f 18 | 19 | /** 20 | * 更新Drawable 21 | */ 22 | fun updateDrawable(drawable: Drawable?) { 23 | setImageDrawable(drawable) 24 | if (drawable == null) return 25 | if (drawable is Animatable) drawable.start() 26 | val w = drawable.intrinsicWidth * 1f 27 | val h = drawable.intrinsicHeight * 1f 28 | val W = measuredWidth * 1f 29 | val H = measuredHeight * 1f 30 | if (w <= 0 || h <= 0 || W <= 0 || H <= 0) return 31 | val minScale = Math.min(H / h, W / w) 32 | val maxScale = Math.max(H / h, W / w) 33 | val dif = maxScale / minScale 34 | when { 35 | dif > 3f -> setScaleLevels(1f, 3f, 5f) 36 | dif > 1f -> setScaleLevels(1f, dif, Math.max(1f + (dif - 1f) * 2, 3f)) 37 | else -> setScaleLevels(1f, 1.75f, 3f) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/FixMultiViewPager.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | 7 | /** 8 | * Author : suzeyu 9 | * Time : 2016-12-26 上午1:41 10 | * ClassDescription : 对多点触控场景时, {@link android.support.v4.view.ViewPager#onInterceptTouchEvent(MotionEvent)}中 11 | * pointerIndex = -1. 发生IllegalArgumentException: pointerIndex out of range 处理 12 | */ 13 | class FixMultiViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : androidx.viewpager.widget.ViewPager(context, attrs) { 14 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 15 | try { 16 | return super.onInterceptTouchEvent(ev) 17 | } catch (ex: IllegalArgumentException) { 18 | ex.printStackTrace() 19 | } 20 | return false 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/FixSwipeRefreshLayout.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.view.ViewConfiguration 7 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 8 | 9 | /** 10 | * 解决滑动冲突 11 | * @property touchSlop Int 12 | * @property prevX Float 13 | * @property declined Boolean 14 | * @constructor 15 | */ 16 | class FixSwipeRefreshLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : 17 | SwipeRefreshLayout(context, attrs) { 18 | private val touchSlop by lazy { ViewConfiguration.get(context).scaledTouchSlop } 19 | 20 | private var prevX = 0f 21 | private var declined = false 22 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 23 | when (ev.action) { 24 | MotionEvent.ACTION_DOWN -> { 25 | prevX = ev.x 26 | declined = false 27 | } 28 | MotionEvent.ACTION_MOVE -> if (declined || Math.abs(ev.x - prevX) > touchSlop) { 29 | declined = true 30 | return false 31 | } 32 | } 33 | return super.onInterceptTouchEvent(ev) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/HotViewFlipper.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.ViewFlipper 6 | import soko.ekibun.bangumi.R 7 | 8 | /** 9 | * https://medium.com/@AllanHasegawa/previewing-multiples-item-types-in-a-recyclerview-163aebc2f34a 10 | */ 11 | class HotViewFlipper : ViewFlipper { 12 | private val initialView: Int 13 | 14 | constructor(context: Context) : super(context) { 15 | initialView = 0 16 | } 17 | 18 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 19 | val styled = context.theme.obtainStyledAttributes( 20 | attrs, R.styleable.HotViewFlipperAttrs, 0, 0 21 | ) 22 | 23 | initialView = try { 24 | styled.getInteger( 25 | R.styleable.HotViewFlipperAttrs_initialView, 0 26 | ) 27 | } finally { 28 | styled.recycle() 29 | } 30 | } 31 | 32 | override fun onFinishInflate() { 33 | super.onFinishInflate() 34 | displayedChild = initialView % childCount 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/NotifyActionProvider.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.ImageView 9 | import android.widget.TextView 10 | import androidx.core.view.ActionProvider 11 | import soko.ekibun.bangumi.R 12 | 13 | /** 14 | * 通知提示ActionProvider 15 | * @property mIvIcon ImageView? 16 | * @property mTvBadge TextView? 17 | * @property onClick Function0 18 | * @property badge Int 19 | * @constructor 20 | */ 21 | class NotifyActionProvider(context: Context): ActionProvider(context){ 22 | private var mIvIcon: ImageView? = null 23 | private var mTvBadge: TextView? = null 24 | 25 | var onClick = {} 26 | 27 | @SuppressLint("InflateParams", "PrivateResource") 28 | override fun onCreateActionView(): View { 29 | val size = context.resources.getDimensionPixelSize( 30 | com.google.android.material.R.dimen.abc_action_bar_default_height_material) 31 | 32 | val layoutParams = ViewGroup.LayoutParams(size, size) 33 | val view = LayoutInflater.from(context) 34 | .inflate(R.layout.action_notify, null, false) 35 | view.layoutParams = layoutParams 36 | mIvIcon = view.findViewById(R.id.iv_icon) as ImageView 37 | mTvBadge = view.findViewById(R.id.tv_badge) as TextView 38 | view.setOnClickListener{ onClick() } 39 | updateBadge(badge) 40 | return view 41 | } 42 | 43 | var badge = 0 44 | set(value) { 45 | field = value 46 | updateBadge(value) 47 | } 48 | private fun updateBadge(i: Int){ 49 | mTvBadge?.text = i.toString() 50 | mTvBadge?.visibility = if(i == 0) View.INVISIBLE else View.VISIBLE 51 | mIvIcon?.setImageResource(if(i==0) R.drawable.ic_notifications_none else R.drawable.ic_notifications) 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/NumberPicker.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.LayoutInflater 6 | import android.widget.FrameLayout 7 | import androidx.core.widget.doAfterTextChanged 8 | import kotlinx.android.synthetic.main.number_picker.view.* 9 | import soko.ekibun.bangumi.R 10 | 11 | /** 12 | * 数字选择 13 | * @property onValueChangeListener Function1 14 | * @property value Int 15 | * @constructor 16 | */ 17 | class NumberPicker @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : 18 | FrameLayout(context, attrs) { 19 | 20 | init { 21 | LayoutInflater.from(context).inflate(R.layout.number_picker, this, true) 22 | 23 | number_display.doAfterTextChanged { 24 | onValueChangeListener(value) 25 | } 26 | 27 | increment.setOnClickListener { 28 | value += 1 29 | } 30 | 31 | decrement.setOnClickListener { 32 | value -= 1 33 | } 34 | } 35 | 36 | private var onValueChangeListener: (Int) -> Unit = { } 37 | fun setValueChangedListener(listener: (Int) -> Unit) { 38 | onValueChangeListener = listener 39 | } 40 | 41 | var value 42 | get() = number_display.text.toString().toIntOrNull() ?: 0 43 | set(value) { 44 | number_display.setText(value.toString()) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/OnFastScrollStateChangeListener.java: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view; 2 | 3 | public interface OnFastScrollStateChangeListener { 4 | 5 | /** 6 | * Called when fast scrolling begins 7 | */ 8 | void onFastScrollStart(); 9 | 10 | /** 11 | * Called when fast scrolling ends 12 | */ 13 | void onFastScrollStop(); 14 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/RecyclerTabLayout.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.util.AttributeSet 6 | import androidx.viewpager.widget.ViewPager 7 | 8 | class RecyclerTabLayout constructor(context: Context, attrs: AttributeSet) : 9 | com.nshmura.recyclertablayout.RecyclerTabLayout(context, attrs) { 10 | override fun setUpWithAdapter(adapter: Adapter<*>) { 11 | mAdapter = adapter 12 | mViewPager = adapter.viewPager 13 | requireNotNull(mViewPager.adapter) { "ViewPager does not have a PagerAdapter set" } 14 | mViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { 15 | var scrollState = ViewPager.SCROLL_STATE_DRAGGING 16 | override fun onPageScrollStateChanged(state: Int) { 17 | scrollState = state 18 | } 19 | 20 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { 21 | if (scrollState == ViewPager.SCROLL_STATE_DRAGGING) { 22 | scrollToTab(position, positionOffset, false) 23 | } 24 | } 25 | 26 | override fun onPageSelected(position: Int) { 27 | if (scrollState == ViewPager.SCROLL_STATE_SETTLING) 28 | startAnimation(position) 29 | } 30 | 31 | }) 32 | setAdapter(adapter) 33 | scrollToTab(mViewPager.currentItem) 34 | } 35 | 36 | override fun startAnimation(position: Int) { 37 | var distance = 0f 38 | val view = mLinearLayoutManager.findViewByPosition(position) 39 | if (view != null) { 40 | val currentX = view.x + view.measuredWidth / 2f 41 | val centerX = measuredWidth / 2f 42 | distance = (centerX - currentX) / view.measuredWidth 43 | } 44 | if (distance == 0f) return scrollToTab(position) 45 | val animator: ValueAnimator 46 | animator = ValueAnimator.ofFloat(distance, 0f) 47 | animator.duration = DEFAULT_SCROLL_DURATION 48 | animator.addUpdateListener { animation -> 49 | scrollToTab(position, animation.animatedValue as Float, true) 50 | } 51 | animator.start() 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/ui/view/RoundBackgroundDecoration.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.ui.view 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.drawable.Drawable 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import androidx.recyclerview.widget.RecyclerView 7 | import soko.ekibun.bangumi.R 8 | import soko.ekibun.bangumi.util.ResourceUtil 9 | 10 | class RoundBackgroundDecoration(val offset: Int = 0) : RecyclerView.ItemDecoration() { 11 | private lateinit var roundCornerDrawable: Drawable 12 | private val dp24 = ResourceUtil.toPixels(24f) 13 | 14 | override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 15 | if (!::roundCornerDrawable.isInitialized) 16 | roundCornerDrawable = parent.context.getDrawable(R.drawable.bg_round_dialog)!! 17 | val layoutManager = (parent.layoutManager as? LinearLayoutManager) ?: return 18 | val scrollStart = Math.min(layoutManager.findViewByPosition(0)?.let { 19 | -layoutManager.getDecoratedTop(it) - offset 20 | } ?: if (layoutManager.findFirstVisibleItemPosition() > 0) dp24 else 0, dp24) 21 | 22 | roundCornerDrawable.setBounds(0, -scrollStart, c.width, c.height) 23 | roundCornerDrawable.draw(c) 24 | super.onDraw(c, parent, state) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/FileRequestBody.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util 2 | 3 | import okhttp3.MediaType 4 | import okhttp3.RequestBody 5 | import okio.* 6 | import java.io.IOException 7 | 8 | /** 9 | * 扩展OkHttp的请求体,实现上传时的进度提示 10 | */ 11 | class FileRequestBody( 12 | private val requestBody: RequestBody, private val callback: (total: Long, progress: Long) -> Unit 13 | ) : RequestBody() { 14 | 15 | /** 16 | * 包装完成的BufferedSink 17 | */ 18 | private var bufferedSink: BufferedSink? = null 19 | 20 | @Throws(IOException::class) 21 | override fun contentLength(): Long { 22 | return requestBody.contentLength() 23 | } 24 | 25 | override fun contentType(): MediaType? { 26 | return requestBody.contentType() 27 | } 28 | 29 | @Throws(IOException::class) 30 | override fun writeTo(sink: BufferedSink) { 31 | bufferedSink = bufferedSink ?: sink(sink).buffer() //包装 32 | requestBody.writeTo(bufferedSink!!) //写入 33 | bufferedSink!!.flush() //必须调用flush,否则最后一部分数据可能不会被写入 34 | } 35 | 36 | /** 37 | * 写入,回调进度接口 38 | * 39 | * @param sink Sink 40 | * @return Sink 41 | */ 42 | private fun sink(sink: Sink): Sink { 43 | return object : ForwardingSink(sink) { 44 | //当前写入字节数 45 | var bytesWritten = 0L 46 | 47 | //总字节长度,避免多次调用contentLength()方法 48 | var contentLength = 0L 49 | 50 | @Throws(IOException::class) 51 | override fun write(source: Buffer, byteCount: Long) { 52 | var size = byteCount 53 | if (contentLength == 0L) { 54 | //获得contentLength的值,后续不再调用 55 | contentLength = contentLength() 56 | } 57 | while (size > 0) { 58 | val sizeToWrite = Math.min(40960L, size) 59 | super.write(source, sizeToWrite) 60 | //增加当前写入的字节数 61 | bytesWritten += sizeToWrite 62 | //回调 63 | callback(contentLength, bytesWritten) 64 | size -= sizeToWrite 65 | } 66 | } 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/FixAppBarLayoutBehavior.java: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 7 | import androidx.core.view.ViewCompat; 8 | import com.google.android.material.appbar.AppBarLayout; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | /** 12 | * Workaround AppBarLayout.Behavior for https://issuetracker.google.com/66996774 13 | * 14 | * See https://gist.github.com/chrisbanes/8391b5adb9ee42180893300850ed02f2 for 15 | * example usage. 16 | * 17 | * Change the package name as you wish. 18 | */ 19 | @SuppressWarnings("deprecation") 20 | public class FixAppBarLayoutBehavior extends AppBarLayout.Behavior { 21 | 22 | public FixAppBarLayoutBehavior() { 23 | super(); 24 | } 25 | 26 | public FixAppBarLayoutBehavior(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | @Override 31 | public void onNestedScroll(@NotNull CoordinatorLayout coordinatorLayout, @NotNull AppBarLayout child, @NotNull View target, 32 | int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { 33 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, 34 | dxUnconsumed, dyUnconsumed, type); 35 | stopNestedScrollIfNeeded(dyUnconsumed, child, target, type); 36 | } 37 | 38 | @Override 39 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, @NotNull AppBarLayout child, 40 | View target, int dx, int dy, int[] consumed, int type) { 41 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type); 42 | stopNestedScrollIfNeeded(dy, child, target, type); 43 | } 44 | 45 | private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) { 46 | if (type == ViewCompat.TYPE_NON_TOUCH) { 47 | final int currOffset = getTopAndBottomOffset(); 48 | if ((dy < 0 && currOffset == 0) 49 | || (dy > 0 && currOffset == -child.getTotalScrollRange())) { 50 | ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/JsonUtil.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonObject 5 | import com.google.gson.JsonParser 6 | import com.google.gson.reflect.TypeToken 7 | 8 | /** 9 | * Json工具类 10 | */ 11 | object JsonUtil { 12 | val GSON = Gson() 13 | private val JSON_PARSER = JsonParser() 14 | 15 | /** 16 | * 转换为JSON 17 | * @param src Any 18 | * @return String 19 | */ 20 | fun toJson(src: Any): String { 21 | return GSON.toJson(src) 22 | } 23 | 24 | /** 25 | * 转换为实例T 26 | * @param json String 27 | * @return T? 28 | */ 29 | inline fun toEntity(json: String): T? { 30 | return try { 31 | GSON.fromJson(json, object : TypeToken() {}.type) 32 | } catch (e: Exception) { 33 | null 34 | } 35 | } 36 | 37 | /** 38 | * 转为JsonObject 39 | * @param json String 40 | * @return JsonObject 41 | */ 42 | fun toJsonObject(json: String): JsonObject { 43 | return JSON_PARSER.parse(json).asJsonObject 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/PluginPreference.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.provider.Settings 7 | import androidx.preference.PreferenceViewHolder 8 | import androidx.preference.SwitchPreference 9 | import kotlinx.android.synthetic.main.pref_plugin_widget.view.* 10 | import soko.ekibun.bangumi.R 11 | 12 | class PluginPreference(context: Context?, private val plugin: Map.Entry) : SwitchPreference(context) { 13 | 14 | init { 15 | val appInfo = plugin.key.applicationInfo 16 | key = "use_plugin_${plugin.key.packageName}" 17 | title = plugin.key.getString(appInfo.labelRes) 18 | icon = plugin.key.applicationInfo.loadIcon(plugin.key.packageManager) 19 | summary = plugin.key.packageName 20 | setDefaultValue(true) 21 | widgetLayoutResource = R.layout.pref_plugin_widget 22 | } 23 | 24 | override fun onBindViewHolder(holder: PreferenceViewHolder) { 25 | super.onBindViewHolder(holder) 26 | holder.itemView.item_settings.setOnClickListener { 27 | context.startActivity( 28 | Intent( 29 | Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 30 | Uri.parse("package:" + plugin.key.packageName) 31 | ) 32 | ) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/TimeUtil.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util 2 | 3 | import android.annotation.SuppressLint 4 | import java.text.SimpleDateFormat 5 | 6 | /** 7 | * 时间格式化 8 | */ 9 | @SuppressLint("SimpleDateFormat") 10 | object TimeUtil { 11 | val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd") } 12 | val timeFormat by lazy { SimpleDateFormat("HH:mm") } 13 | 14 | val weekJp = listOf("", "月", "火", "水", "木", "金", "土", "日") 15 | val weekList = listOf("", "周一", "周二", "周三", "周四", "周五", "周六", "周日") 16 | val weekSmall = listOf("", "一", "二", "三", "四", "五", "六", "日") 17 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/WebViewCookieHandler.java: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util; 2 | 3 | import android.webkit.CookieManager; 4 | import okhttp3.Cookie; 5 | import okhttp3.CookieJar; 6 | import okhttp3.HttpUrl; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * cookie 同步 14 | */ 15 | public class WebViewCookieHandler implements CookieJar { 16 | private CookieManager mCookieManager = CookieManager.getInstance(); 17 | 18 | @Override 19 | public void saveFromResponse(HttpUrl url, List cookies) { 20 | /* no-op */ 21 | } 22 | 23 | @Override 24 | public List loadForRequest(HttpUrl url) { 25 | String urlString = url.toString(); 26 | String cookiesString = mCookieManager.getCookie(urlString); 27 | 28 | if (cookiesString != null && !cookiesString.isEmpty()) { 29 | String[] cookieHeaders = cookiesString.split(";"); 30 | List cookies = new ArrayList<>(cookieHeaders.length); 31 | 32 | for (String header : cookieHeaders) { 33 | cookies.add(Cookie.parse(url, header)); 34 | } 35 | 36 | return cookies; 37 | } 38 | 39 | return Collections.emptyList(); 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/span/BaseLineImageSpan.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util.span 2 | 3 | import android.graphics.Paint 4 | import android.text.style.ImageSpan 5 | 6 | class BaseLineImageSpan(drawable: UrlDrawable, private val source: String? = null) : 7 | ImageSpan(drawable, ALIGN_BASELINE) { 8 | override fun getSource(): String? = source 9 | override fun getDrawable(): UrlDrawable = super.getDrawable() as UrlDrawable 10 | override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { 11 | val width = super.getSize(paint, text, start, end, fm) 12 | fm?.descent = paint.fontMetricsInt.descent 13 | fm?.bottom = paint.fontMetricsInt.bottom 14 | fm?.top = Math.min(fm?.top ?: 0, paint.fontMetricsInt.top) 15 | fm?.ascent = Math.min(fm?.ascent ?: 0, paint.fontMetricsInt.ascent) 16 | return width 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/span/ClickableImageSpan.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util.span 2 | 3 | import android.text.style.ClickableSpan 4 | import android.util.Log 5 | import android.view.View 6 | 7 | /** 8 | * 9 | */ 10 | class ClickableImageSpan( 11 | var image: BaseLineImageSpan, 12 | private val onClick: (View, BaseLineImageSpan) -> Unit 13 | ) : ClickableSpan() { 14 | override fun onClick(widget: View) { 15 | Log.v("click", image.drawable.toString()) 16 | val drawable = image.drawable 17 | if (drawable.error == true) drawable.loadImage() 18 | else if (drawable.error == false) onClick(widget, image) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/span/ClickableUrlSpan.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util.span 2 | 3 | import android.text.TextPaint 4 | import android.text.style.ClickableSpan 5 | import android.view.View 6 | import soko.ekibun.bangumi.api.bangumi.Bangumi 7 | import soko.ekibun.bangumi.ui.web.WebActivity 8 | 9 | /** 10 | * Url回调Span 11 | */ 12 | class ClickableUrlSpan( 13 | val url: String, 14 | var onClick: (View, String) -> Unit = { v, str -> 15 | WebActivity.launchUrl(v.context, Bangumi.parseUrl(str), "") 16 | } 17 | ) : ClickableSpan() { 18 | override fun onClick(widget: View) { 19 | onClick(widget, url) 20 | } 21 | 22 | override fun updateDrawState(ds: TextPaint) { 23 | ds.color = ds.linkColor 24 | ds.isUnderlineText = false 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/span/CodeLineSpan.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util.span 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Paint 5 | import android.graphics.Rect 6 | import android.graphics.Typeface 7 | import android.text.Layout 8 | import android.text.style.LeadingMarginSpan 9 | import android.text.style.LineBackgroundSpan 10 | import android.text.style.TypefaceSpan 11 | import androidx.core.text.toSpanned 12 | import soko.ekibun.bangumi.model.ThemeModel 13 | 14 | class CodeLineSpan : TypefaceSpan("monospace"), LeadingMarginSpan, LineBackgroundSpan { 15 | override fun drawLeadingMargin( 16 | c: Canvas, 17 | p: Paint, 18 | x: Int, 19 | dir: Int, 20 | top: Int, 21 | baseline: Int, 22 | bottom: Int, 23 | text: CharSequence, 24 | start: Int, 25 | end: Int, 26 | first: Boolean, 27 | layout: Layout? 28 | ) { 29 | val spanStart = text.toSpanned().getSpanStart(this) 30 | if (spanStart < 0 || start < spanStart) return 31 | if (start > 0 && text[start - 1] != '\n') return 32 | val lineCount = text.substring(spanStart, start).count { it == '\n' } + 1 33 | val typeFace = p.typeface 34 | val align = p.textAlign 35 | val color = p.color 36 | p.typeface = Typeface.MONOSPACE 37 | p.textAlign = Paint.Align.RIGHT 38 | p.color = 0x80808080.toInt() 39 | c.drawText(lineCount.toString(), x + 40f, baseline.toFloat(), p) 40 | p.typeface = typeFace 41 | p.textAlign = align 42 | p.color = color 43 | } 44 | 45 | override fun getLeadingMargin(first: Boolean): Int = 50 46 | 47 | override fun drawBackground( 48 | canvas: Canvas, 49 | paint: Paint, 50 | left: Int, 51 | right: Int, 52 | top: Int, 53 | baseline: Int, 54 | bottom: Int, 55 | text: CharSequence, 56 | start: Int, 57 | end: Int, 58 | lineNumber: Int 59 | ) { 60 | val alpha = ThemeModel.TranslucentPaint.alpha 61 | ThemeModel.TranslucentPaint.alpha = 10 62 | canvas.drawRect(Rect(left, top, right, bottom), ThemeModel.TranslucentPaint) 63 | ThemeModel.TranslucentPaint.alpha = alpha 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/span/MaskSpan.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util.span 2 | 3 | import android.graphics.Color 4 | import android.text.TextPaint 5 | import android.text.style.ClickableSpan 6 | import android.view.View 7 | import android.widget.TextView 8 | import com.awarmisland.android.richedittext.view.RichEditText 9 | import soko.ekibun.bangumi.model.ThemeModel 10 | import java.lang.ref.WeakReference 11 | 12 | /** 13 | * 马赛克Span 14 | */ 15 | class MaskSpan : ClickableSpan() { 16 | var textView: WeakReference? = null 17 | 18 | private val edit = textView?.get() is RichEditText 19 | override fun onClick(widget: View) { 20 | if (edit) return 21 | val view = textView?.get() ?: return 22 | view.tag = if (view.tag == this) null else this 23 | view.text = view.text 24 | } 25 | 26 | override fun updateDrawState(ds: TextPaint) { 27 | ds.bgColor = ThemeModel.ForegroundPaint.color 28 | ds.color = if (edit || textView?.get()?.tag == this) ThemeModel.BackgroundPaint.color else Color.TRANSPARENT 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/soko/ekibun/bangumi/util/span/QuoteLineSpan.kt: -------------------------------------------------------------------------------- 1 | package soko.ekibun.bangumi.util.span 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Paint 5 | import android.graphics.Rect 6 | import android.text.Layout 7 | import android.text.style.LeadingMarginSpan 8 | import android.text.style.LineBackgroundSpan 9 | import soko.ekibun.bangumi.model.ThemeModel 10 | 11 | class QuoteLineSpan : LeadingMarginSpan, LineBackgroundSpan { 12 | override fun drawBackground( 13 | c: Canvas, 14 | p: Paint, 15 | left: Int, 16 | right: Int, 17 | top: Int, 18 | baseline: Int, 19 | bottom: Int, 20 | text: CharSequence, 21 | start: Int, 22 | end: Int, 23 | lnum: Int 24 | ) { 25 | c.drawRect(Rect(left, top, right, bottom), ThemeModel.TranslucentPaint) 26 | } 27 | 28 | override fun drawLeadingMargin( 29 | c: Canvas, 30 | p: Paint, 31 | x: Int, 32 | dir: Int, 33 | top: Int, 34 | baseline: Int, 35 | bottom: Int, 36 | text: CharSequence?, 37 | start: Int, 38 | end: Int, 39 | first: Boolean, 40 | layout: Layout? 41 | ) { 42 | c.drawRect(Rect(x, top, x + 10, bottom), ThemeModel.TranslucentPaint) 43 | } 44 | 45 | override fun getLeadingMargin(first: Boolean): Int = 20 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/move_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/anim/move_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/color/color_bg_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/color/color_calendar_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/color/color_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/color/color_checkable_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/color/color_selectable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/akkarin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/akkarin.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/bangumi_detail_ic_season_first.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/bangumi_detail_ic_season_first.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/bangumi_detail_ic_season_last.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/bangumi_detail_ic_season_last.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/bangumi_detail_ic_season_middle.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/bangumi_detail_ic_season_middle.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/empty.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/empty.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/err_401.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/err_401.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/err_404.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/err_404.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/drawable-xhdpi/welcome.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_calendar_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_episode_badge.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_episode_outline.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_notify_badge.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_round_checkable.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_round_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_round_outline.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_round_rect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_say_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_say_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_selectable.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_broken_image.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chevron_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chevron_right.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_code.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_emoji_keyboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_explore.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_heart_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_history.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_image.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_insert_emoji.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_keyboard.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lock.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_none.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_time.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timelapse.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_widgets.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/placeholder_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/action_notify.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_web.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 24 | 25 | 28 | 29 | 37 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/base_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 18 | 19 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_calendar.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 28 | 29 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 21 | 22 | 27 | 28 | 30 | 31 | 32 | 33 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | 25 | 35 | 36 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_collection.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 15 | 24 | 25 | 35 | 36 | 42 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_rakuen.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 19 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_timeline.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 15 | 24 | 25 | 36 | 37 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_avatar_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 26 | 27 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_calendar_now.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 26 | 27 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_calendar_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 33 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_emoji.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_episode_flipper.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_episode_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_rakuen_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 21 | 22 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_search_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 16 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_season.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_site.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 19 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_subject_small.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 29 | 42 | 43 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 31 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/number_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 30 | 31 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pref_coffee_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/pref_plugin_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 28 | 29 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | 20 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_subject.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/menu/action_web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/drawer_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 11 | 15 | 19 | 23 | 24 | 25 | 26 | 31 | 36 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 16 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_notify.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_search_type.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 13 | 15 | 17 | 19 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_timeline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_topic_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/nav_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 11 | 12 | 20 | 21 | 26 | 27 | 31 | 32 | 40 | 41 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/xml/filepaths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlinVersion = '1.4.31' 5 | repositories { 6 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 7 | maven { url 'https://maven.aliyun.com/repository/google' } 8 | google() 9 | mavenCentral() 10 | maven { url 'https://repo1.maven.org/maven2/' } 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:4.0.1' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | maven { url 'https://maven.aliyun.com/repository/jcenter' } 21 | maven { url 'https://maven.aliyun.com/repository/google' } 22 | maven { url 'https://jitpack.io' } 23 | maven { url 'https://repo1.maven.org/maven2/' } 24 | mavenCentral() 25 | google() 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | 33 | ext { 34 | androidXVersion = '1.0.0' 35 | materialVersion = '1.2.0-alpha04' 36 | roomVersion = '2.2.3' 37 | glideVersion = '4.11.0' 38 | okhttpVersion = '4.5.0' 39 | coroutinesVersion = '1.4.3' 40 | appCompatVersion = '1.1.0' 41 | archLifecycleVersion = '2.3.0' 42 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 10 18:56:33 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /images/prev1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/images/prev1.jpg -------------------------------------------------------------------------------- /images/prev2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/images/prev2.jpg -------------------------------------------------------------------------------- /images/prev3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/images/prev3.jpg -------------------------------------------------------------------------------- /images/prev4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekibun/Bangumi/81099b44d5f351868f8ff2beb0cc19704d44e030/images/prev4.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='Bangumi' --------------------------------------------------------------------------------