├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── fanhl.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── fanhl │ │ └── doujinMoe │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── android │ │ │ └── support │ │ │ │ └── v4 │ │ │ │ └── view │ │ │ │ ├── DirectionalViewPager.java │ │ │ │ └── VerticalViewPagerCompat.java │ │ └── com │ │ │ └── fanhl │ │ │ ├── doujinMoe │ │ │ ├── App.java │ │ │ ├── api │ │ │ │ ├── BaseApi.java │ │ │ │ ├── BookApi.java │ │ │ │ ├── HomeApi.java │ │ │ │ ├── PageApi.java │ │ │ │ ├── common │ │ │ │ │ └── DouJinMoeUrl.java │ │ │ │ └── form │ │ │ │ │ └── NewestForm.java │ │ │ ├── common │ │ │ │ └── Constants.java │ │ │ ├── exception │ │ │ │ ├── GetBookFailException.java │ │ │ │ ├── GetDataFailException.java │ │ │ │ └── NetworkAccessFailException.java │ │ │ ├── interfaceX │ │ │ │ └── OnDownloadManagerBookChangeListener.java │ │ │ ├── model │ │ │ │ ├── Book.java │ │ │ │ ├── IndexItem.java │ │ │ │ └── Page.java │ │ │ ├── rest │ │ │ │ ├── DoujinmoeClient.java │ │ │ │ ├── model │ │ │ │ │ └── FolderResponse.java │ │ │ │ └── service │ │ │ │ │ └── HomeService.java │ │ │ ├── ui │ │ │ │ ├── DetailsActivity.java │ │ │ │ ├── DownloadManagerActivity.java │ │ │ │ ├── GalleryActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── adapter │ │ │ │ │ ├── BookGridRecyclerAdapter.java │ │ │ │ │ ├── GalleryPagerAdapter.java │ │ │ │ │ ├── MainPagerAdapter.java │ │ │ │ │ ├── PageGridRecyclerAdapter.java │ │ │ │ │ └── downloadManager │ │ │ │ │ │ └── AbsDownloadManagerRecyclerAdapter.java │ │ │ │ ├── common │ │ │ │ │ ├── AbsActivity.java │ │ │ │ │ └── AbsRecyclerViewAdapter.java │ │ │ │ ├── fragment │ │ │ │ │ ├── AbsFragment.java │ │ │ │ │ ├── BookPageFragment.java │ │ │ │ │ ├── downloadManager │ │ │ │ │ │ ├── AbsDownloadManagerFragment.java │ │ │ │ │ │ ├── DownloadFailFragment.java │ │ │ │ │ │ ├── DownloadSuccessFragment.java │ │ │ │ │ │ └── DownloadingFragment.java │ │ │ │ │ └── main │ │ │ │ │ │ ├── AbcFragment.java │ │ │ │ │ │ ├── AbsBookRecyclerFragment.java │ │ │ │ │ │ ├── AbsHomeFragment.java │ │ │ │ │ │ ├── ArtistFragment.java │ │ │ │ │ │ ├── BestFragment.java │ │ │ │ │ │ ├── CategoryFragment.java │ │ │ │ │ │ ├── DownloadedFragment.java │ │ │ │ │ │ ├── NewestFragment.java │ │ │ │ │ │ └── SearchFragment.java │ │ │ │ └── widget │ │ │ │ │ ├── NavigationFooterView.java │ │ │ │ │ ├── StatusBarHeaderView.java │ │ │ │ │ └── TextDrawable.java │ │ │ └── util │ │ │ │ ├── ColorGenerator.java │ │ │ │ ├── DownloadManager.java │ │ │ │ ├── FileCacheManager.java │ │ │ │ ├── FileUtil.java │ │ │ │ ├── FullScreenHelper.java │ │ │ │ ├── LocalManager.java │ │ │ │ ├── NumberUtil.java │ │ │ │ └── Utility.java │ │ │ ├── loadmore │ │ │ ├── OnBottomListener.java │ │ │ └── OnRcvScrollListener.java │ │ │ ├── photoview │ │ │ └── PhotoViewAttacherEx.java │ │ │ └── util │ │ │ ├── DisplayUtil.java │ │ │ ├── GsonUtil.java │ │ │ ├── ThreadUtil.java │ │ │ └── UrlUtils.java │ └── res │ │ ├── anim │ │ ├── card_slide_in.xml │ │ └── state_anim_raise.xml │ │ ├── drawable-hdpi │ │ ├── fa_book.png │ │ ├── fa_bookmark.png │ │ ├── fa_bookmark_o.png │ │ ├── fa_download.png │ │ ├── fa_download_done.png │ │ ├── ic_launcher.png │ │ └── place_holder.png │ │ ├── drawable-mdpi │ │ ├── fa_book.png │ │ ├── fa_bookmark.png │ │ ├── fa_bookmark_o.png │ │ ├── fa_download.png │ │ ├── fa_download_done.png │ │ ├── ic_launcher.png │ │ └── place_holder.png │ │ ├── drawable-v21 │ │ ├── ic_menu_camera.xml │ │ ├── ic_menu_gallery.xml │ │ ├── ic_menu_manage.xml │ │ ├── ic_menu_send.xml │ │ ├── ic_menu_share.xml │ │ └── ic_menu_slideshow.xml │ │ ├── drawable-xhdpi │ │ ├── fa_book.png │ │ ├── fa_bookmark.png │ │ ├── fa_bookmark_o.png │ │ ├── fa_download.png │ │ ├── fa_download_done.png │ │ ├── ic_launcher.png │ │ └── place_holder.png │ │ ├── drawable-xxhdpi │ │ ├── fa_book.png │ │ ├── fa_bookmark.png │ │ ├── fa_bookmark_o.png │ │ ├── fa_download.png │ │ ├── fa_download_done.png │ │ ├── ic_launcher.png │ │ └── place_holder.png │ │ ├── drawable-xxxhdpi │ │ ├── fa_book.png │ │ ├── fa_bookmark.png │ │ ├── fa_bookmark_o.png │ │ ├── fa_download.png │ │ ├── fa_download_done.png │ │ ├── ic_launcher.png │ │ └── place_holder.png │ │ ├── drawable │ │ └── side_nav_bar.xml │ │ ├── layout-v21 │ │ ├── list_item_book_card.xml │ │ ├── list_item_download_card.xml │ │ └── list_item_page_card.xml │ │ ├── layout │ │ ├── activity_details.xml │ │ ├── activity_download_manager.xml │ │ ├── activity_gallery.xml │ │ ├── activity_main.xml │ │ ├── app_bar_main.xml │ │ ├── content_details.xml │ │ ├── content_main.xml │ │ ├── fragment_book_page.xml │ │ ├── fragment_recycle.xml │ │ ├── fragment_swipe_recycle.xml │ │ ├── list_item_book_card.xml │ │ ├── list_item_book_card_content.xml │ │ ├── list_item_download_card.xml │ │ ├── list_item_download_card_content.xml │ │ ├── list_item_page_card.xml │ │ ├── list_item_page_card_content.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ ├── main.xml │ │ ├── menu_details.xml │ │ └── menu_download_manager.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── drawables.xml │ │ ├── integer.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── fanhl │ └── doujinMoe │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── graphics ├── device-2015-11-28-222059.png └── face1.png └── 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 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Doujin-Moe -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/fanhl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | doujinmoe 5 | snakebar 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | Android API 23 Platform 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Doujin-Moe 2 | 3 | 啊哈...这是一个Doujin-Moe的Android客户端. 4 | 5 | 使用了 Retrofit,JSoup,OKHttp,ButterKnife,RxJava,RxAndroid,PhotoViewEx,DirectionalViewPager,Gson,EventBus,... 6 | 7 | 主要代码参考自[NHentai-android](https://github.com/fython/NHentai-android). 8 | 9 | 10 | 11 | #特別聲明 12 | 13 | 該應用程式所供應的內容不適合未成年人觀看,所有內容通過 Jsoup 解析 Doujin-Moe 官網獲得,內容有任何異議或造成心理甚至生理上的問題均與本項目無關。 14 | 15 | 觀看時請留意是否適用於當地法律法規。( > _ < ) 16 | 17 | #功能 18 | 19 | 1.书籍下载. 20 | 21 | #备忘 22 | 23 | 1.书籍中的page命名规则按 1.jpg(png ...) ,2.jpg 的方式来命名,不采用网站上的"乱码". OK 24 | 25 | 2.若已下载,添加一个重新下载的处理??? OK 26 | 27 | 3.有空把所有null判断/验证判断全换成抛异常. 28 | 29 | 4.在detail页面给下载中的MenuItem加动画.bookList页面给book加动画. 30 | 31 | 5.最近阅读,喜欢(bookmark),已下载(本地\[本地导入和网站上下载的分开放??\]). 32 | 33 | 6.navigate不够放,改成网格型吧.. X 34 | 35 | 7.加排序(排序方式(date,ranking,name) 36 | 37 | 8.把超过一个月(?)没看的未下载的书(只缓存了book.json,position等)删掉. 38 | 39 | 9.在Gallery页面,加上 重新下载当前页menuItem 的功能 40 | 41 | 10.把 Detail页面, download 都换成 retrofit. 50% 42 | 43 | 11.把 category和search版块合并到一起. 44 | 45 | 12.把一堆监听换成EventBus. 46 | 47 | 13.向上/下翻页时,前/后一页显示成 crop_top/crop_bottom 的显示还有点小问题. 48 | 49 | 14.横屏换页时,禁止滑动到一半自动换下一页. 50 | 51 | 15.翻页时有时会直接翻到最后一页. 52 | 53 | #修改 54 | 55 | ~~1.改用fresco,在图库界面使用fresco设置placeHolder.~~ 56 | 57 | ~~2.用PhotoDraweeView来兼容Fresco和PhotoView.~~ 58 | 59 | 3.换回Picasso.Fresco完全不好用.![image](graphics/face1.png) 60 | 61 | 4.用PhotoViewEx来处理横屏时图片显示的位置. 62 | 63 | 5.Retrofit超好用!!! 64 | 65 | 6.Retrofit+Rx也超好用!!!! 66 | 67 | 7.fixed picasso oom. 68 | 69 | #问题 70 | 71 | |问题 | 解决方案 | 72 | | ---------------------------------------------- | --------------------------------------------- | 73 | |~~用picasso来读取漫画页(大图)好慢..而且没法保存.~~ | ~~用fresco代替~~ | 74 | 75 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | 4 | android { 5 | compileSdkVersion 23 6 | buildToolsVersion "23.0.2" 7 | 8 | defaultConfig { 9 | applicationId "com.fanhl.doujinMoe" 10 | minSdkVersion 14 11 | targetSdkVersion 23 12 | versionCode 4 13 | versionName "0.2.1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | testCompile 'junit:junit:4.12' 30 | compile 'com.android.support:appcompat-v7:23.1.1' 31 | compile 'com.android.support:design:23.1.1' 32 | compile 'com.android.support:support-v13:23.1.1' 33 | 34 | compile 'com.android.support:support-v4:23.1.1' 35 | 36 | compile 'com.android.support:recyclerview-v7:23.1.1' 37 | compile 'com.android.support:cardview-v7:23.1.1' 38 | 39 | compile 'com.squareup.picasso:picasso:2.5.2' 40 | // compile 'com.facebook.fresco:fresco:0.8.1' 41 | compile 'com.github.chrisbanes.photoview:library:1.2.4' 42 | 43 | compile 'org.jsoup:jsoup:1.8.3' 44 | compile 'com.squareup.okhttp:okhttp:2.5.0' 45 | compile 'com.google.code.gson:gson:2.5' 46 | 47 | compile 'com.jakewharton:butterknife:7.0.1' 48 | 49 | 50 | // compile 'ch.halcyon:squareprogressbar:1.6.0' 51 | 52 | compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' 53 | compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' 54 | compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' 55 | compile 'io.reactivex:rxandroid:1.0.1' 56 | compile 'io.reactivex:rxjava:1.0.14' 57 | } 58 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/fanhl/Applications/SDK/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/fanhl/doujinMoe/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/android/support/v4/view/VerticalViewPagerCompat.java: -------------------------------------------------------------------------------- 1 | 2 | package android.support.v4.view; 3 | 4 | import android.database.DataSetObserver; 5 | 6 | public final class VerticalViewPagerCompat { 7 | private VerticalViewPagerCompat() { 8 | } 9 | 10 | public static void setDataSetObserver(PagerAdapter adapter, DataSetObserver observer) { 11 | adapter.registerDataSetObserver(observer); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/App.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe; 2 | 3 | import android.app.Application; 4 | 5 | import com.fanhl.doujinMoe.rest.DoujinmoeClient; 6 | import com.fanhl.doujinMoe.util.DownloadManager; 7 | import com.fanhl.doujinMoe.util.LocalManager; 8 | import com.squareup.picasso.LruCache; 9 | import com.squareup.picasso.Picasso; 10 | 11 | /** 12 | * Created by fanhl on 15/11/18. 13 | */ 14 | public class App extends Application { 15 | /*Retrofit*/ 16 | DoujinmoeClient client; 17 | 18 | private Picasso picasso; 19 | 20 | DownloadManager downloadManager; 21 | LocalManager localManager; 22 | 23 | @Override 24 | public void onCreate() { 25 | super.onCreate(); 26 | 27 | client = new DoujinmoeClient(); 28 | 29 | picasso = new Picasso.Builder(this) 30 | .memoryCache(new LruCache(24000)) 31 | .build(); 32 | 33 | localManager = LocalManager.getInstance(this); 34 | downloadManager = DownloadManager.getInstance(this, localManager); 35 | } 36 | 37 | public DoujinmoeClient getClient() { 38 | return client; 39 | } 40 | 41 | public DownloadManager getDownloadManager() { 42 | return downloadManager; 43 | } 44 | 45 | public LocalManager getLocalManager() { 46 | return localManager; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/api/BaseApi.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.api; 2 | 3 | import android.util.Log; 4 | 5 | import com.fanhl.doujinMoe.api.common.DouJinMoeUrl; 6 | 7 | import org.jsoup.Jsoup; 8 | 9 | import java.io.IOException; 10 | import java.util.Date; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by fanhl on 15/11/8. 15 | */ 16 | public class BaseApi { 17 | public static final String TAG = BaseApi.class.getSimpleName(); 18 | public static final int TIME_OUT = 30 * 1000; 19 | public static final long TOKEN_TIME_OUT = 5 * 60 * 1000; 20 | public static final String TOKEN_KEY = "PHPSESSID"; 21 | public static String token = null; 22 | public static Date tokenRefreshDate = null; 23 | 24 | /** 25 | * 服务器端token是写死的? 26 | * 27 | * @return 28 | * @throws IOException 29 | */ 30 | public static String token() throws IOException { 31 | // return "mm1qk0kqrf6hs6r82dbkli6fb4"; 32 | if (token == null || isTokenTimeOut()) { 33 | Map cookies = Jsoup.connect(DouJinMoeUrl.HOME).timeout(TIME_OUT).execute().cookies(); 34 | Log.d(TAG, "Cookies:" + cookies); 35 | token = cookies.get(TOKEN_KEY); 36 | } 37 | tokenRefreshDate = new Date(); 38 | return token; 39 | } 40 | 41 | private static boolean isTokenTimeOut() { 42 | return tokenRefreshDate == null || tokenRefreshDate.getTime() + TOKEN_TIME_OUT < new Date().getTime(); 43 | } 44 | 45 | public static String header() { 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/api/BookApi.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.api; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.BitmapDrawable; 5 | import android.graphics.drawable.Drawable; 6 | import android.util.Log; 7 | 8 | import com.fanhl.doujinMoe.exception.GetBookFailException; 9 | import com.fanhl.doujinMoe.model.Book; 10 | import com.fanhl.doujinMoe.util.FileCacheManager; 11 | 12 | import java.io.File; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by fanhl on 15/11/17. 17 | */ 18 | public class BookApi extends BaseApi { 19 | public static File createBookDir(Context context, Book book) { 20 | Log.d(TAG, "创建书籍目录 book:" + book.name); 21 | FileCacheManager m = FileCacheManager.getInstance(context); 22 | return m.createBookDir(book); 23 | } 24 | 25 | public static Book getBookFormJson(Context context, Book book) { 26 | Log.d(TAG, "从本地json中取得book信息:" + book.name); 27 | FileCacheManager m = FileCacheManager.getInstance(context); 28 | return m.getBookFormJson(book); 29 | } 30 | 31 | public static boolean saveBookJson(Context context, Book book) { 32 | Log.d(TAG, "保存书籍Json:" + book.name); 33 | FileCacheManager m = FileCacheManager.getInstance(context); 34 | return m.saveBookJson(book); 35 | } 36 | 37 | public static List getLocalBooks(Context context) throws GetBookFailException { 38 | Log.d(TAG, "取得本地的书籍."); 39 | 40 | FileCacheManager m = FileCacheManager.getInstance(context); 41 | return m.getLocalBooks(); 42 | } 43 | 44 | public static boolean deleteBook(Context context, Book book) { 45 | Log.d(TAG, "删除书籍:" + book.name); 46 | FileCacheManager m = FileCacheManager.getInstance(context); 47 | return m.deleteBook(book); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/api/HomeApi.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.api; 2 | 3 | import android.util.Log; 4 | 5 | import com.fanhl.doujinMoe.api.common.DouJinMoeUrl; 6 | import com.fanhl.doujinMoe.api.form.NewestForm; 7 | import com.fanhl.doujinMoe.exception.GetDataFailException; 8 | import com.fanhl.doujinMoe.model.Book; 9 | import com.fanhl.util.GsonUtil; 10 | 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.nodes.Document; 13 | import org.jsoup.nodes.Element; 14 | import org.jsoup.select.Elements; 15 | 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * Created by fanhl on 15/11/8. 22 | */ 23 | public class HomeApi extends BaseApi { 24 | public static final String TAG = HomeApi.class.getSimpleName(); 25 | 26 | @Deprecated 27 | public static NewestForm newest(int pageIndex) throws IOException, GetDataFailException { 28 | Log.d(TAG, "取得最新书籍."); 29 | String token = token(); 30 | String result = Jsoup.connect(DouJinMoeUrl.NEWEST) 31 | .cookie(TOKEN_KEY, token) 32 | .data("get", String.valueOf(pageIndex)) 33 | .timeout(TIME_OUT) 34 | .post().text(); 35 | Log.d(TAG, "取得最新书箱的Json:" + result); 36 | NewestForm newestForm = GsonUtil.obj(result, NewestForm.class); 37 | 38 | if (newestForm == null || !newestForm.success || newestForm.newest == null) { 39 | throw new GetDataFailException("取得 最新书籍 的数据失败."); 40 | } 41 | 42 | return newestForm; 43 | } 44 | 45 | @Deprecated 46 | public static List best(int pageIndex, String sortType) throws IOException, GetDataFailException { 47 | Log.d(TAG, "取得排行榜书籍."); 48 | String token = token(); 49 | Document document = Jsoup.connect(DouJinMoeUrl.BEST) 50 | .cookie(TOKEN_KEY, token) 51 | .data("page", String.valueOf(pageIndex)) 52 | .data("sort", sortType)// FIXME: 15/11/10 之后再改 53 | .timeout(TIME_OUT) 54 | .post(); 55 | 56 | if (document == null) throw new GetDataFailException("取不到排行榜的数据"); 57 | 58 | List books = new ArrayList<>(); 59 | 60 | Element dircontent = document.getElementById("dircontent"); 61 | Elements elements = dircontent.select("a[href]"); 62 | 63 | // 64 | //
65 | //
66 | // 67 | //
68 | //
Koufukuron
69 | //
70 | //
71 | //
IMAGES
72 | //
35
73 | //
74 | //
75 | //
RATING
76 | //
4.70
77 | //
78 | //
79 | //
DATE
80 | //
Oct 19th 2015
81 | //
82 | //
83 | //
84 | //
85 | //
86 | //
87 | for (Element element : elements) { 88 | Elements holder1 = element.getElementsByClass("holder"); 89 | if (holder1 == null || holder1.isEmpty()) continue; 90 | Element holder = holder1.get(0); 91 | if (holder == null) continue; 92 | 93 | Book book = new Book(); 94 | 95 | book.token = getBookToken(element); 96 | 97 | Element titleEle = element.getElementsByClass("title").get(0); 98 | book.name = titleEle.getElementsByClass("text").text(); 99 | 100 | Element info = titleEle.getElementsByClass("info").get(0); 101 | 102 | Elements infos = info.select("div[class^=thumbinfo]"); 103 | 104 | book.count = Integer.valueOf(infos.get(0).getElementsByClass("value").text()); 105 | book.rating = infos.get(1).getElementsByClass("value").text(); 106 | book.date = infos.get(2).getElementsByClass("value").text(); 107 | 108 | Log.d(TAG, book.toString()); 109 | books.add(book); 110 | } 111 | 112 | return books; 113 | } 114 | 115 | private static String getBookToken(Element element) { 116 | String href = element.attr("href"); 117 | if (href.startsWith("/")) return href.substring(1); 118 | return href; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/api/PageApi.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.api; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.util.Log; 6 | 7 | import com.fanhl.doujinMoe.api.common.DouJinMoeUrl; 8 | import com.fanhl.doujinMoe.exception.GetDataFailException; 9 | import com.fanhl.doujinMoe.model.Book; 10 | import com.fanhl.doujinMoe.model.Page; 11 | import com.fanhl.doujinMoe.util.FileCacheManager; 12 | import com.fanhl.doujinMoe.util.NumberUtil; 13 | import com.squareup.okhttp.OkHttpClient; 14 | import com.squareup.okhttp.Request; 15 | import com.squareup.okhttp.Response; 16 | 17 | import org.jsoup.Jsoup; 18 | import org.jsoup.nodes.Document; 19 | import org.jsoup.nodes.Element; 20 | import org.jsoup.select.Elements; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | 25 | import okio.BufferedSink; 26 | import okio.Okio; 27 | 28 | /** 29 | * Created by fanhl on 15/11/8. 30 | */ 31 | public class PageApi extends BaseApi { 32 | private static final String TAG = PageApi.class.getSimpleName(); 33 | 34 | public static Void pages(Book book) throws GetDataFailException, IOException { 35 | Log.d(TAG, "取得书籍详细内容."); 36 | String token = /*"mm1qk0kqrf6hs6r82dbkli6fb4";//*/ token(); 37 | Document document = Jsoup.connect(DouJinMoeUrl.detailUrl(book.token)) 38 | .cookie(TOKEN_KEY, token) 39 | .data("action", "get") 40 | .timeout(TIME_OUT) 41 | .post(); 42 | 43 | if (document == null) throw new GetDataFailException("取不到书籍详细页面的数据"); 44 | 45 | Element foldercontent = document.getElementById("foldercontent"); 46 | Elements djms = foldercontent.select("djm"); 47 | 48 | for (Element djm : djms) { 49 | Page page = new Page(); 50 | page.preview = djm.attr("thumb"); 51 | page.href = djm.attr("file"); 52 | 53 | Log.d(TAG, page.toString()); 54 | book.pages.add(page); 55 | } 56 | 57 | return null; 58 | } 59 | 60 | public static boolean isCached(Context context, String url) { 61 | return FileCacheManager.getInstance(context).isCached(url); 62 | } 63 | 64 | public static Drawable getCachedDrawable(Context context, String url) { 65 | return FileCacheManager.getInstance(context).getCachedDrawable(url); 66 | } 67 | 68 | public static boolean isPageDownloaded(Context context, Book book, int index) { 69 | return FileCacheManager.getInstance(context).isPageDownloaded(book, index); 70 | } 71 | 72 | public static boolean downloadPage(Context context, Book book, int index) { 73 | FileCacheManager m = FileCacheManager.getInstance(context); 74 | 75 | File cacheFile = m.createCacheFile(); 76 | 77 | Page page = book.pages.get(index); 78 | 79 | //download file 80 | OkHttpClient client = new OkHttpClient(); 81 | Request request = new Request.Builder().url(page.href).build(); 82 | Response response; 83 | BufferedSink sink = null; 84 | try { 85 | response = client.newCall(request).execute(); 86 | sink = Okio.buffer(Okio.sink(cacheFile)); 87 | sink.writeAll(response.body().source()); 88 | Log.d(TAG, "第 " + (index + 1) + "/" + book.pages.size() + " 张图片下载完成."); 89 | } catch (IOException e) { 90 | e.printStackTrace(); 91 | return false; 92 | } finally { 93 | if (sink != null) { 94 | try { 95 | sink.close(); 96 | } catch (IOException e) { 97 | e.printStackTrace(); 98 | } 99 | } 100 | } 101 | 102 | File pageFile = m.createPageFile(book, index); 103 | 104 | return pageFile != null && cacheFile.renameTo(pageFile); 105 | 106 | } 107 | 108 | public static File getPageFile(Context context, Book book, int index) { 109 | FileCacheManager m = FileCacheManager.getInstance(context); 110 | 111 | File bookImagesDir = m.getBookImagesDir(book); 112 | File pageFile = new File(bookImagesDir, getPageName(book, index)); 113 | return pageFile; 114 | } 115 | 116 | @Deprecated 117 | public static String getPageFilePath(Context context, Book book, int index) { 118 | FileCacheManager m = FileCacheManager.getInstance(context); 119 | 120 | File bookImagesDir = m.getBookImagesDir(book); 121 | return bookImagesDir + getPageName(book, index); 122 | } 123 | 124 | /** 125 | * 生成page名字 126 | * 127 | * @param book 128 | * @param index 129 | */ 130 | public static String getPageName(Book book, int index) { 131 | return NumberUtil.formatPrefix(index) + "." + getExtension(book.pages.get(index)); 132 | } 133 | 134 | /** 135 | * 取得书籍扩展名 136 | * 137 | * @param page 138 | * @return 139 | */ 140 | public static String getExtension(Page page) { 141 | if (page == null || page.preview == null) return "";// FIXME: 15/11/20 preview 和 href 的文件 扩展名是一致的么??? 142 | 143 | String[] parts = page.preview.split("\\."); 144 | 145 | if (parts.length < 2) return ""; 146 | 147 | return parts[parts.length - 1]; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/api/common/DouJinMoeUrl.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.api.common; 2 | 3 | /** 4 | * Created by fanhl on 15/11/8. 5 | */ 6 | public class DouJinMoeUrl { 7 | public static final String HOME = "http://www.doujinmoe.us/"; 8 | public static final String NEWEST = HOME + "ajax/newest.php"; 9 | public static final String BEST = HOME + "best"; 10 | 11 | public static String previewUrl(String token) { 12 | return "http://static.doujin-moe.us/f-" + token + ".jpg"; 13 | } 14 | 15 | public static String detailUrl(String token) { 16 | return HOME + token; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/api/form/NewestForm.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.api.form; 2 | 3 | import com.fanhl.doujinMoe.model.Book; 4 | 5 | import java.util.List; 6 | 7 | public class NewestForm { 8 | 9 | /** 10 | * success : true 11 | * message : 12 | * top : {"token":"s33j1tsh","name":"Practical Exercises"} 13 | * artist : {"token":"7gm18aax","name":"Darabuchi"} 14 | * newest : [{"token":"q6muejvz","name":"Best Position","count":"24","rating":"4.50","date":"Nov 6th 2015"},{"token":"gyme9rx1","name":"Locker Girl Nanami-chan","count":"16","rating":"4.50","date":"Nov 6th 2015"}] 15 | */ 16 | public boolean success; 17 | public String message; 18 | /** 19 | * token : s33j1tsh 20 | * name : Practical Exercises 21 | */ 22 | public TokenName top; 23 | /** 24 | * token : 7gm18aax 25 | * name : Darabuchi 26 | */ 27 | public TokenName artist; 28 | /** 29 | * token : q6muejvz 30 | * name : Best Position 31 | * count : 24 32 | * rating : 4.50 33 | * date : Nov 6th 2015 34 | */ 35 | public List newest; 36 | 37 | public static class TokenName { 38 | public String token; 39 | public String name; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/common/Constants.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.common; 2 | 3 | /** 4 | * Created by fanhl on 15/11/8. 5 | */ 6 | public class Constants { 7 | public static final int TIME_OUT = 30000; 8 | /*每次加载书籍的数量*/ 9 | public static final int PAGE_BOOK_COUNT_MAX = 36; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/exception/GetBookFailException.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.exception; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Created by fanhl on 15/11/20. 7 | */ 8 | public class GetBookFailException extends IOException { 9 | public GetBookFailException() { 10 | } 11 | 12 | public GetBookFailException(String detailMessage) { 13 | super(detailMessage); 14 | } 15 | 16 | public GetBookFailException(String detailMessage, Throwable e) { 17 | super(detailMessage, e); 18 | } 19 | 20 | public GetBookFailException(Throwable throwable) { 21 | super(throwable); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/exception/GetDataFailException.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.exception; 2 | 3 | /** 4 | * Created by fanhl on 15/11/8. 5 | */ 6 | public class GetDataFailException extends Exception { 7 | public GetDataFailException() { 8 | } 9 | 10 | public GetDataFailException(String detailMessage) { 11 | super(detailMessage); 12 | } 13 | 14 | public GetDataFailException(String detailMessage, Throwable e) { 15 | super(detailMessage, e); 16 | } 17 | 18 | public GetDataFailException(Throwable throwable) { 19 | super(throwable); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/exception/NetworkAccessFailException.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.exception; 2 | 3 | /** 4 | * Created by fanhl on 15/11/6. 5 | */ 6 | public class NetworkAccessFailException extends Exception { 7 | public NetworkAccessFailException() { 8 | } 9 | 10 | public NetworkAccessFailException(String msg, Throwable e) { 11 | super(msg, e); 12 | } 13 | 14 | public NetworkAccessFailException(Throwable throwable) { 15 | super(throwable); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/interfaceX/OnDownloadManagerBookChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.interfaceX; 2 | 3 | import com.fanhl.doujinMoe.model.Book; 4 | 5 | /** 6 | * 用于在父onAttach时绑定activity,并在DownloadManager下载成功/失败时通知Fragment 7 | *

8 | * Created by fanhl on 15/11/27. 9 | */ 10 | public interface OnDownloadManagerBookChangeListener { 11 | /** 12 | * 用来响应 DownloadManager 下载书籍成功/失败 13 | * 14 | * @param book 15 | * @param success 16 | */ 17 | void onDownloadBookChanged(Book book, boolean success); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/model/Book.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by fanhl on 15/11/5. 11 | */ 12 | public class Book { 13 | 14 | //--------------------- Gson 解析取得----------------------- 15 | 16 | public String token; 17 | @SerializedName("title") 18 | public String name; 19 | @SerializedName("objectcount") 20 | public int count; 21 | public boolean main; 22 | public String rating; 23 | public String date; 24 | 25 | //---------------------- 本地信息 --------------------------- 26 | 27 | public List pages; 28 | /*当前看到的页码*/ 29 | public int position; 30 | /*最新阅读日期*/ 31 | public Date recent; 32 | /*下载状态*/ 33 | public Status status; 34 | 35 | //--------------其它相关----------------- 36 | 37 | /*当 status=.DOWNLOADING 时记录下载到的页码*/ 38 | public int downloadedPosition = -1; 39 | 40 | public Book() { 41 | pages = new ArrayList<>(); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "Book(" + name + "," + token + ")"; 47 | } 48 | 49 | public boolean isDownloading() { 50 | return status == Status.DOWNLOADING; 51 | } 52 | 53 | public boolean isDownloaded() { 54 | return status == Status.DOWNLOADED; 55 | } 56 | 57 | public boolean isWaitDownload() { 58 | return status == Status.WAIT_DOWNLOAD; 59 | } 60 | 61 | public enum Status { 62 | NONE, WAIT_DOWNLOAD, DOWNLOADING, DOWNLOADED 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/model/IndexItem.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.model; 2 | 3 | /** 4 | * 将一个对象连同一个index打包起来 5 | *

6 | * Created by fanhl on 15/11/19. 7 | */ 8 | public class IndexItem { 9 | public I item; 10 | public int index; 11 | 12 | public IndexItem(I item, int index) { 13 | this.item = item; 14 | this.index = index; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/model/Page.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.model; 2 | 3 | /** 4 | * Created by fanhl on 15/11/6. 5 | */ 6 | public class Page { 7 | /*预览图(src链接)*/ 8 | public String preview; 9 | /*大图(src链接)*/ 10 | public String href; 11 | 12 | @Override 13 | public String toString() { 14 | return "Page(" + preview + "," + href + ")"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/rest/DoujinmoeClient.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.rest; 2 | 3 | import com.fanhl.doujinMoe.rest.service.HomeService; 4 | 5 | import retrofit.GsonConverterFactory; 6 | import retrofit.Retrofit; 7 | import retrofit.RxJavaCallAdapterFactory; 8 | 9 | /** 10 | * Created by fanhl on 15/12/1. 11 | */ 12 | public class DoujinmoeClient { 13 | 14 | public static final String BASE_URL = "http://m.doujin-moe.us"; 15 | private final HomeService homeService; 16 | 17 | public DoujinmoeClient() { 18 | Retrofit retrofit = new Retrofit.Builder() 19 | .baseUrl(BASE_URL) 20 | .addConverterFactory(GsonConverterFactory.create()) 21 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 22 | .build(); 23 | 24 | homeService = retrofit.create(HomeService.class); 25 | } 26 | 27 | public HomeService getHomeService() { 28 | return homeService; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/rest/model/FolderResponse.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.rest.model; 2 | 3 | import com.fanhl.doujinMoe.model.Book; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 存放状态,书籍列表 9 | *

10 | * Created by fanhl on 15/12/1. 11 | */ 12 | public class FolderResponse { 13 | /** 14 | * success : true 15 | * message : 16 | * folders : [{"token":"a2ze9msz","title":"A Rainy Afternoon with Friends","objectcount":"20","main":true,"rating":"2.70","date":"11/29/2015"},{"token":"mrejuww9","title":"About-Face","objectcount":"24","main":true,"rating":"4.84","date":"11/29/2015"},{"token":"ywthbq8j","title":"Do You Know About Buddha's Face","objectcount":"24","main":true,"rating":"4.42","date":"11/29/2015"},{"token":"w8wrwmm4","title":"I Like My Sister","objectcount":"22","main":true,"rating":"3.94","date":"11/29/2015"},{"token":"wrevngbe","title":"Jelly Multiplication","objectcount":"20","main":true,"rating":"3.81","date":"11/29/2015"}] 17 | * complete : false 18 | */ 19 | 20 | public boolean success; 21 | public String message; 22 | public boolean complete; 23 | /** 24 | * token : a2ze9msz 25 | * title : A Rainy Afternoon with Friends 26 | * objectcount : 20 27 | * main : true 28 | * rating : 2.70 29 | * date : 11/29/2015 30 | */ 31 | 32 | public List folders; 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/rest/service/HomeService.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.rest.service; 2 | 3 | import com.fanhl.doujinMoe.rest.model.FolderResponse; 4 | import com.squareup.okhttp.Response; 5 | 6 | import retrofit.Call; 7 | import retrofit.http.Field; 8 | import retrofit.http.FormUrlEncoded; 9 | import retrofit.http.GET; 10 | import retrofit.http.POST; 11 | import retrofit.http.Path; 12 | import rx.Observable; 13 | 14 | /** 15 | * 移动端 数据传输服务 (好像所有服务全走的这一个接口) 16 | *

17 | * Created by fanhl on 15/12/1. 18 | */ 19 | public interface HomeService { 20 | @FormUrlEncoded 21 | @POST("/ajax/folder.php") 22 | Observable bookList( 23 | @Field("token") String token, 24 | @Field("offset") int offset, 25 | @Field("max") int max, 26 | @Field("sort") String sort, 27 | @Field("param") String param); 28 | 29 | // FIXME: 15/12/7 未完成 30 | // @Multipart 31 | @GET("{url}") 32 | Call downloadPage(@Path("url") String pagePath); 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/GalleryActivity.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.content.res.Configuration; 6 | import android.graphics.Color; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.support.annotation.Nullable; 11 | import android.support.v4.view.DirectionalViewPager; 12 | import android.support.v4.view.ViewCompat; 13 | import android.support.v4.view.ViewPager; 14 | import android.support.v7.app.ActionBar; 15 | import android.support.v7.widget.AppCompatSeekBar; 16 | import android.support.v7.widget.AppCompatTextView; 17 | import android.support.v7.widget.Toolbar; 18 | import android.util.Log; 19 | import android.view.View; 20 | import android.view.WindowManager; 21 | import android.widget.LinearLayout; 22 | import android.widget.SeekBar; 23 | 24 | import com.fanhl.doujinMoe.R; 25 | import com.fanhl.doujinMoe.api.BookApi; 26 | import com.fanhl.doujinMoe.model.Book; 27 | import com.fanhl.doujinMoe.ui.adapter.GalleryPagerAdapter; 28 | import com.fanhl.doujinMoe.ui.common.AbsActivity; 29 | import com.fanhl.doujinMoe.util.FullScreenHelper; 30 | import com.fanhl.doujinMoe.util.Utility; 31 | import com.fanhl.util.GsonUtil; 32 | 33 | import butterknife.Bind; 34 | import butterknife.ButterKnife; 35 | 36 | public class GalleryActivity extends AbsActivity { 37 | public static final String TAG = GalleryActivity.class.getSimpleName(); 38 | private static final int AUTO_HIDE_DELAY_MILLIS = 1500; 39 | public static final String EXTRA_BOOK_DATA = "EXTRA_BOOK_DATA"; 40 | 41 | @Bind(R.id.toolbar) 42 | Toolbar toolbar; 43 | @Bind(R.id.pager) 44 | DirectionalViewPager mPager; 45 | @Bind(R.id.app_bar) 46 | LinearLayout mAppBar; 47 | @Bind(R.id.bottom_bar) 48 | LinearLayout mBottomBar; 49 | @Bind(R.id.seekBar) 50 | AppCompatSeekBar mSeekBar; 51 | @Bind(R.id.total_pages_text) 52 | AppCompatTextView mTotalPagesText; 53 | 54 | //custom 55 | 56 | private Book book; 57 | 58 | private FullScreenHelper mFullScreenHelper; 59 | private GalleryPagerAdapter mPagerAdapter; 60 | 61 | public static void launch(Activity activity, Book book) { 62 | Intent intent = new Intent(activity, GalleryActivity.class); 63 | intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 64 | intent.putExtra(EXTRA_BOOK_DATA, GsonUtil.json(book)); 65 | activity.startActivity(intent); 66 | } 67 | 68 | @Override 69 | protected void onCreate(Bundle savedInstanceState) { 70 | super.onCreate(savedInstanceState); 71 | 72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !Utility.isChrome()) { 73 | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 74 | } 75 | 76 | if (Build.VERSION.SDK_INT >= 21) { 77 | getWindow().setStatusBarColor(Color.TRANSPARENT); 78 | getWindow().setNavigationBarColor(Color.TRANSPARENT); 79 | } 80 | 81 | mFullScreenHelper = new FullScreenHelper(this); 82 | // 别问我为什么这么干 让我先冷静一下→_→ 83 | mFullScreenHelper.setFullScreen(true); 84 | mFullScreenHelper.setFullScreen(false); 85 | 86 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 87 | 88 | setContentView(R.layout.activity_gallery); 89 | ButterKnife.bind(this); 90 | setSupportActionBar(toolbar); 91 | 92 | Intent intent = getIntent(); 93 | book = GsonUtil.obj(intent.getStringExtra(EXTRA_BOOK_DATA), Book.class); 94 | assert book != null; 95 | int tmpPosition = book.position;//详细页点击第三张page时,position=2,此时本地bookJson中position=3.这种情况下使用2这个值. 96 | //从本地取最新的数据 97 | book = BookApi.getBookFormJson(this, book); 98 | book.position = tmpPosition; 99 | 100 | ActionBar actionBar = getSupportActionBar(); 101 | if (actionBar != null) { 102 | actionBar.setDisplayHomeAsUpEnabled(true); 103 | } 104 | 105 | setTitle(book.name); 106 | 107 | mPagerAdapter = new GalleryPagerAdapter(getFragmentManager(), book); 108 | mPager.setAdapter(mPagerAdapter); 109 | mPager.setCurrentItem(book.position, false); 110 | // mPager.setOffscreenPageLimit(4); 111 | 112 | //注:DirectionalViewPager实现的是过时的方法setOnPageChangeListener 113 | mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { 114 | @Override 115 | public void onPageSelected(int position) { 116 | Log.d(TAG, "page currentIndex:" + position); 117 | mSeekBar.setProgress(position); 118 | mTotalPagesText.setText(getString(R.string.info_total_pages, position + 1, book.count)); 119 | book.position = position; 120 | } 121 | }); 122 | 123 | refreshForOrientation(getResources().getConfiguration()); 124 | 125 | mTotalPagesText.setText(getString(R.string.info_total_pages, book.position + 1, book.count)); 126 | mSeekBar.setKeyProgressIncrement(1); 127 | mSeekBar.setMax(book.count - 1); 128 | mSeekBar.setProgress(book.position); 129 | mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 130 | int progress = 0; 131 | 132 | @Override 133 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 134 | this.progress = progress; 135 | mTotalPagesText.setText(getString(R.string.info_total_pages, progress + 1, book.count)); 136 | } 137 | 138 | @Override 139 | public void onStartTrackingTouch(SeekBar seekBar) { 140 | } 141 | 142 | @Override 143 | public void onStopTrackingTouch(SeekBar seekBar) { 144 | book.position = progress; 145 | mPager.setCurrentItem(progress, false); 146 | } 147 | }); 148 | } 149 | 150 | @Override 151 | protected void onPostCreate(@Nullable Bundle savedInstanceState) { 152 | super.onPostCreate(savedInstanceState); 153 | new Handler().postDelayed(this::hide, AUTO_HIDE_DELAY_MILLIS); 154 | } 155 | 156 | @Override 157 | protected void onStop() { 158 | super.onStop(); 159 | BookApi.saveBookJson(this, book); 160 | } 161 | 162 | @Override 163 | public void onConfigurationChanged(Configuration newConfig) { 164 | super.onConfigurationChanged(newConfig); 165 | refreshForOrientation(newConfig); 166 | } 167 | 168 | private void refreshForOrientation(Configuration newConfig) { 169 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 170 | mPager.setOrientation(DirectionalViewPager.HORIZONTAL); 171 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 172 | mPager.setOrientation(DirectionalViewPager.VERTICAL); 173 | } 174 | } 175 | 176 | public void toggle() { 177 | if (mAppBar.getAlpha() != 0f) { 178 | hide(); 179 | } else if (mAppBar.getAlpha() != 1f) { 180 | show(); 181 | } 182 | } 183 | 184 | private void hide() { 185 | ViewCompat.animate(mAppBar).alpha(0f).start(); 186 | ViewCompat.animate(mBottomBar).alpha(0f).start(); 187 | mFullScreenHelper.setFullScreen(true); 188 | } 189 | 190 | private void show() { 191 | ViewCompat.animate(mAppBar).alpha(1f).start(); 192 | ViewCompat.animate(mBottomBar).alpha(1f).start(); 193 | mFullScreenHelper.setFullScreen(false); 194 | } 195 | 196 | @Override 197 | public void onBackPressed() { 198 | if (mAppBar.getAlpha() != 1f) { 199 | toggle(); 200 | } else { 201 | super.onBackPressed(); 202 | } 203 | } 204 | 205 | @Override 206 | public void onDMDownloadSuccess(Book book) { 207 | if (this.book.name.equals(book.name)) { 208 | book.status = Book.Status.DOWNLOADED; 209 | } 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.NavigationView; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v4.view.GravityCompat; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v4.widget.DrawerLayout; 10 | import android.support.v7.app.ActionBarDrawerToggle; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | 15 | import com.fanhl.doujinMoe.R; 16 | import com.fanhl.doujinMoe.model.Book; 17 | import com.fanhl.doujinMoe.ui.adapter.MainPagerAdapter; 18 | import com.fanhl.doujinMoe.ui.common.AbsActivity; 19 | 20 | import butterknife.Bind; 21 | import butterknife.ButterKnife; 22 | 23 | public class MainActivity extends AbsActivity 24 | implements NavigationView.OnNavigationItemSelectedListener { 25 | 26 | @Bind(R.id.drawer_layout) 27 | DrawerLayout drawer; 28 | @Bind(R.id.nav_view) 29 | NavigationView navigationView; 30 | @Bind(R.id.toolbar) 31 | Toolbar toolbar; 32 | @Bind(R.id.fab) 33 | FloatingActionButton fab; 34 | @Bind(R.id.pager) 35 | ViewPager mViewpager; 36 | 37 | //custom 38 | 39 | MainPagerAdapter mPagerAdapter; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_main); 45 | ButterKnife.bind(this); 46 | setSupportActionBar(toolbar); 47 | 48 | fab.setOnClickListener(view -> Snackbar.make(view, "这个按钮没啥用", Snackbar.LENGTH_LONG) 49 | .setAction("Action", null).show()); 50 | 51 | ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( 52 | this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); 53 | drawer.setDrawerListener(toggle); 54 | toggle.syncState(); 55 | 56 | navigationView.setNavigationItemSelectedListener(this); 57 | 58 | //custom 59 | 60 | mPagerAdapter = new MainPagerAdapter(getFragmentManager()); 61 | mViewpager.setAdapter(mPagerAdapter); 62 | mViewpager.setOffscreenPageLimit(2);//使6个fragment都保持活动 // FIXME: 15/12/1 用 fragment.saveState 来替换 63 | mViewpager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { 64 | @Override 65 | public void onPageSelected(int position) { 66 | mPagerAdapter.pageSelected(MainActivity.this, navigationView, position); 67 | } 68 | }); 69 | 70 | //执行一次,初始化 显示标题, 抽屉选中项 71 | // mViewpager.setCurrentItem(0);//默认为0时不用写 72 | mPagerAdapter.pageSelected(this, navigationView, 0); 73 | } 74 | 75 | @Override 76 | public void onBackPressed() { 77 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 78 | if (drawer.isDrawerOpen(GravityCompat.START)) { 79 | drawer.closeDrawer(GravityCompat.START); 80 | } else { 81 | super.onBackPressed(); 82 | } 83 | } 84 | 85 | @Override 86 | public boolean onCreateOptionsMenu(Menu menu) { 87 | // Inflate the menu; this adds items to the action bar if it is present. 88 | getMenuInflater().inflate(R.menu.main, menu); 89 | return true; 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(MenuItem item) { 94 | // Handle action bar item clicks here. The action bar will 95 | // automatically handle clicks on the Home/Up button, so long 96 | // as you specify a parent activity in AndroidManifest.xml. 97 | int id = item.getItemId(); 98 | 99 | //noinspection SimplifiableIfStatement 100 | if (id == R.id.action_download) { 101 | return true; 102 | } 103 | 104 | return super.onOptionsItemSelected(item); 105 | } 106 | 107 | @SuppressWarnings("StatementWithEmptyBody") 108 | @Override 109 | public boolean onNavigationItemSelected(MenuItem item) { 110 | // Handle navigation view item clicks here. 111 | int id = item.getItemId(); 112 | 113 | if (id == R.id.nav_newest) { 114 | mViewpager.setCurrentItem(MainPagerAdapter.NEWEST_INDEX); 115 | } else if (id == R.id.nav_best) { 116 | mViewpager.setCurrentItem(MainPagerAdapter.BEST_INDEX); 117 | } else if (id == R.id.nav_artist) { 118 | mViewpager.setCurrentItem(MainPagerAdapter.ARTIST_INDEX); 119 | } else if (id == R.id.nav_category) { 120 | mViewpager.setCurrentItem(MainPagerAdapter.CATEGORY_INDEX); 121 | } else if (id == R.id.nav_abc) { 122 | mViewpager.setCurrentItem(MainPagerAdapter.ABC_INDEX); 123 | } else if (id == R.id.nav_search) { 124 | mViewpager.setCurrentItem(MainPagerAdapter.SEARCH_INDEX); 125 | } else if (id == R.id.nav_downloaded) { 126 | mViewpager.setCurrentItem(MainPagerAdapter.DOWNLOADED_INDEX); 127 | } else if (id == R.id.nav_download_manage) { 128 | DownloadManagerActivity.launch(this); 129 | } else if (id == R.id.nav_share) { 130 | 131 | } else if (id == R.id.nav_send) { 132 | 133 | } 134 | 135 | drawer.closeDrawer(GravityCompat.START); 136 | return true; 137 | } 138 | 139 | 140 | @Override 141 | public void onDMDownloadSuccess(Book book) { 142 | Snackbar.make(mViewpager, String.format(getString(R.string.download_book_success), book.name), Snackbar.LENGTH_LONG).setAction(R.string.action_check, v -> { 143 | // FIXME: 15/11/20 跳转到下载列表页面. 144 | }).show(); 145 | } 146 | 147 | @Override 148 | public void onDMDownloadFail(Book book) { 149 | Snackbar.make(mViewpager, String.format(getString(R.string.download_book_fail), book.name), Snackbar.LENGTH_LONG).show(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/adapter/BookGridRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 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 | 11 | import com.fanhl.doujinMoe.R; 12 | import com.fanhl.doujinMoe.api.PageApi; 13 | import com.fanhl.doujinMoe.api.common.DouJinMoeUrl; 14 | import com.fanhl.doujinMoe.model.Book; 15 | import com.fanhl.doujinMoe.ui.common.AbsRecyclerViewAdapter; 16 | import com.fanhl.doujinMoe.ui.widget.TextDrawable; 17 | import com.fanhl.doujinMoe.util.ColorGenerator; 18 | import com.fanhl.doujinMoe.util.Utility; 19 | import com.squareup.picasso.Picasso; 20 | 21 | import java.util.List; 22 | 23 | import butterknife.Bind; 24 | import butterknife.ButterKnife; 25 | 26 | /** 27 | * Created by fanhl on 15/10/29. 28 | */ 29 | public class BookGridRecyclerAdapter extends AbsRecyclerViewAdapter { 30 | public static final String TAG = BookGridRecyclerAdapter.class.getSimpleName(); 31 | 32 | private final List list; 33 | private final ColorGenerator mColorGenerator; 34 | 35 | public BookGridRecyclerAdapter(Context context, RecyclerView mRecyclerView, List list) { 36 | super(context, mRecyclerView); 37 | this.list = list; 38 | 39 | mColorGenerator = ColorGenerator.MATERIAL; 40 | } 41 | 42 | @Override 43 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | View view = LayoutInflater.from(context).inflate(R.layout.list_item_book_card, parent, false); 45 | return new ViewHolder(view); 46 | } 47 | 48 | @Override 49 | public void onBindViewHolder(ViewHolder holder, int position) { 50 | super.onBindViewHolder(holder, position); 51 | Book item = list.get(position); 52 | int color = this.mColorGenerator.getColor(item.name); 53 | holder.bind(this.context, item, color); 54 | } 55 | 56 | @Override 57 | public int getItemCount() { 58 | return list.size(); 59 | } 60 | 61 | @Override 62 | public void onViewRecycled(ViewHolder holder) { 63 | super.onViewRecycled(holder); 64 | } 65 | 66 | public void addItem(Book item) { 67 | list.add(item); 68 | notifyItemInserted(list.size() - 1); 69 | } 70 | 71 | public void addItems(List books) { 72 | int positionStart = list.size(); 73 | list.addAll(books); 74 | notifyItemRangeInserted(positionStart, books.size()); 75 | } 76 | 77 | public void clear() { 78 | int itemCount = list.size(); 79 | list.clear(); 80 | notifyItemRangeRemoved(0, itemCount); 81 | } 82 | 83 | public class ViewHolder extends AbsRecyclerViewAdapter.ClickableViewHolder { 84 | @Bind(R.id.preview) 85 | ImageView mPreview; 86 | @Bind(R.id.title) 87 | TextView mTitle; 88 | 89 | public Book item; 90 | 91 | public ViewHolder(View itemView) { 92 | super(itemView); 93 | ButterKnife.bind(this, itemView); 94 | } 95 | 96 | private void bind(Context context, Book item, int color) { 97 | mTitle.setText(item.name); 98 | 99 | if (item.isDownloaded()) { 100 | Picasso.with(context) 101 | .load(PageApi.getPageFile(context, item, 0)) 102 | .fit() 103 | .centerInside() 104 | .into(mPreview); 105 | } else { 106 | TextDrawable drawablePlaceHolder = TextDrawable.builder().buildRect(Utility.getFirstCharacter(item.name), color); 107 | Picasso.with(context) 108 | .load(DouJinMoeUrl.previewUrl(item.token)) 109 | .placeholder(drawablePlaceHolder) 110 | .fit() 111 | .centerInside() 112 | .into(mPreview); 113 | } 114 | 115 | this.item = item; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/adapter/GalleryPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.adapter; 2 | 3 | import android.app.Fragment; 4 | import android.app.FragmentManager; 5 | import android.support.v13.app.FragmentPagerAdapter; 6 | 7 | import com.fanhl.doujinMoe.model.Book; 8 | import com.fanhl.doujinMoe.ui.fragment.BookPageFragment; 9 | 10 | public class GalleryPagerAdapter extends FragmentPagerAdapter { 11 | 12 | private BookPageFragment[] fragments; 13 | private Book book; 14 | 15 | public GalleryPagerAdapter(FragmentManager fm, Book book) { 16 | super(fm); 17 | this.fragments = new BookPageFragment[book.count]; 18 | this.book = book; 19 | } 20 | 21 | @Override 22 | public Fragment getItem(int position) { 23 | if (fragments[position] == null) fragments[position] = BookPageFragment.newInstance(book, position); 24 | return fragments[position]; 25 | } 26 | 27 | @Override 28 | public int getCount() { 29 | return book.count; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/adapter/MainPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.adapter; 2 | 3 | import android.app.Fragment; 4 | import android.app.FragmentManager; 5 | import android.support.design.widget.NavigationView; 6 | import android.support.v13.app.FragmentPagerAdapter; 7 | 8 | import com.fanhl.doujinMoe.R; 9 | import com.fanhl.doujinMoe.ui.MainActivity; 10 | import com.fanhl.doujinMoe.ui.fragment.main.AbcFragment; 11 | import com.fanhl.doujinMoe.ui.fragment.main.ArtistFragment; 12 | import com.fanhl.doujinMoe.ui.fragment.main.BestFragment; 13 | import com.fanhl.doujinMoe.ui.fragment.main.CategoryFragment; 14 | import com.fanhl.doujinMoe.ui.fragment.main.DownloadedFragment; 15 | import com.fanhl.doujinMoe.ui.fragment.main.NewestFragment; 16 | import com.fanhl.doujinMoe.ui.fragment.main.SearchFragment; 17 | 18 | /** 19 | * Created by fanhl on 15/11/5. 20 | */ 21 | public class MainPagerAdapter extends FragmentPagerAdapter { 22 | 23 | public static final int NEWEST_INDEX = 0; 24 | public static final int BEST_INDEX = 1; 25 | public static final int ARTIST_INDEX = 2; 26 | public static final int CATEGORY_INDEX = 3; 27 | public static final int ABC_INDEX = 4; 28 | public static final int SEARCH_INDEX = 5; 29 | public static final int DOWNLOADED_INDEX = 6; 30 | 31 | public static final int PAGE_COUNT = 7; 32 | 33 | private NewestFragment newestFragment; 34 | private BestFragment bestFragment; 35 | private ArtistFragment artistFragment; 36 | private CategoryFragment categoryFragment; 37 | private AbcFragment abcFragment; 38 | private SearchFragment searchFragment; 39 | private DownloadedFragment downloadedFragment; 40 | 41 | public MainPagerAdapter(FragmentManager fm) { 42 | super(fm); 43 | } 44 | 45 | @Override 46 | public Fragment getItem(int position) { 47 | switch (position) { 48 | case NEWEST_INDEX: 49 | if (newestFragment == null) newestFragment = NewestFragment.newInstance(); 50 | return newestFragment; 51 | case BEST_INDEX: 52 | if (bestFragment == null) bestFragment = BestFragment.newInstance(); 53 | return bestFragment; 54 | case ARTIST_INDEX: 55 | if (artistFragment == null) artistFragment = ArtistFragment.newInstance(); 56 | return artistFragment; 57 | case CATEGORY_INDEX: 58 | if (categoryFragment == null) categoryFragment = CategoryFragment.newInstance(); 59 | return categoryFragment; 60 | case ABC_INDEX: 61 | if (abcFragment == null) abcFragment = AbcFragment.newInstance(); 62 | return abcFragment; 63 | case SEARCH_INDEX: 64 | if (searchFragment == null) searchFragment = SearchFragment.newInstance(); 65 | return searchFragment; 66 | case DOWNLOADED_INDEX: 67 | if (downloadedFragment == null) downloadedFragment = DownloadedFragment.newInstance(); 68 | return downloadedFragment; 69 | default: 70 | return null; 71 | } 72 | } 73 | 74 | /** 75 | * 方法为title改名等 76 | * 77 | * @param activity 78 | * @param navigationView 79 | * @param position 80 | */ 81 | public void pageSelected(MainActivity activity, NavigationView navigationView, int position) { 82 | switch (position) { 83 | case NEWEST_INDEX: 84 | activity.setTitle(activity.getString(R.string.title_newest)); 85 | navigationView.setCheckedItem(R.id.nav_newest); 86 | break; 87 | case BEST_INDEX: 88 | activity.setTitle(activity.getString(R.string.title_best)); 89 | navigationView.setCheckedItem(R.id.nav_best); 90 | break; 91 | case ARTIST_INDEX: 92 | activity.setTitle(activity.getString(R.string.title_artist)); 93 | navigationView.setCheckedItem(R.id.nav_artist); 94 | break; 95 | case CATEGORY_INDEX: 96 | activity.setTitle(activity.getString(R.string.title_category)); 97 | navigationView.setCheckedItem(R.id.nav_category); 98 | break; 99 | case ABC_INDEX: 100 | activity.setTitle(activity.getString(R.string.title_abc)); 101 | navigationView.setCheckedItem(R.id.nav_abc); 102 | break; 103 | case SEARCH_INDEX: 104 | activity.setTitle(activity.getString(R.string.title_search)); 105 | navigationView.setCheckedItem(R.id.nav_search); 106 | break; 107 | case DOWNLOADED_INDEX: 108 | activity.setTitle(activity.getString(R.string.title_downloaded)); 109 | navigationView.setCheckedItem(R.id.nav_downloaded); 110 | break; 111 | default: 112 | break; 113 | } 114 | } 115 | 116 | @Override 117 | public int getCount() { 118 | return PAGE_COUNT; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/adapter/PageGridRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | 10 | import com.fanhl.doujinMoe.R; 11 | import com.fanhl.doujinMoe.api.PageApi; 12 | import com.fanhl.doujinMoe.model.Book; 13 | import com.fanhl.doujinMoe.ui.common.AbsRecyclerViewAdapter; 14 | import com.squareup.picasso.MemoryPolicy; 15 | import com.squareup.picasso.Picasso; 16 | 17 | import butterknife.Bind; 18 | import butterknife.ButterKnife; 19 | 20 | /** 21 | * Created by fanhl on 15/11/6. 22 | */ 23 | public class PageGridRecyclerAdapter extends AbsRecyclerViewAdapter { 24 | private final Book book; 25 | 26 | public PageGridRecyclerAdapter(Context context, RecyclerView mRecyclerView, Book book) { 27 | super(context, mRecyclerView); 28 | this.book = book; 29 | } 30 | 31 | @Override 32 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | View view = LayoutInflater.from(context).inflate(R.layout.list_item_page_card, parent, false); 34 | return new ViewHolder(view); 35 | } 36 | 37 | @Override 38 | public void onBindViewHolder(ViewHolder holder, int position) { 39 | super.onBindViewHolder(holder, position); 40 | holder.bind(this.context, book, position); 41 | } 42 | 43 | @Override 44 | public int getItemCount() { 45 | return book.pages.size(); 46 | } 47 | 48 | public class ViewHolder extends AbsRecyclerViewAdapter.ClickableViewHolder { 49 | @Bind(R.id.preview) 50 | ImageView mPreview; 51 | 52 | public ViewHolder(View itemView) { 53 | super(itemView); 54 | ButterKnife.bind(this, itemView); 55 | } 56 | 57 | public void bind(Context context, Book book, int position) { 58 | if (book.isDownloaded()) { 59 | Picasso.with(context) 60 | .load(PageApi.getPageFile(context, book, position)) 61 | .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)//不缓存大图到内存 62 | // .resize(DisplayUtil.dip2px(context, 250), DisplayUtil.dip2px(context, 250)) 63 | .fit() 64 | .centerInside() 65 | // .centerCrop() 66 | .into(mPreview); 67 | } else { 68 | Picasso.with(context) 69 | .load(book.pages.get(position).preview) 70 | .fit() 71 | .centerInside() 72 | .into(mPreview); 73 | } 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/adapter/downloadManager/AbsDownloadManagerRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.adapter.downloadManager; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import com.fanhl.doujinMoe.R; 15 | import com.fanhl.doujinMoe.api.PageApi; 16 | import com.fanhl.doujinMoe.api.common.DouJinMoeUrl; 17 | import com.fanhl.doujinMoe.model.Book; 18 | import com.fanhl.doujinMoe.ui.common.AbsRecyclerViewAdapter; 19 | import com.fanhl.doujinMoe.ui.widget.TextDrawable; 20 | import com.fanhl.doujinMoe.util.ColorGenerator; 21 | import com.fanhl.doujinMoe.util.DownloadManager; 22 | import com.fanhl.doujinMoe.util.Utility; 23 | import com.squareup.picasso.Picasso; 24 | 25 | import butterknife.Bind; 26 | import butterknife.ButterKnife; 27 | 28 | /** 29 | * Created by fanhl on 15/11/24. 30 | */ 31 | public abstract class AbsDownloadManagerRecyclerAdapter extends AbsRecyclerViewAdapter { 32 | public static final String TAG = AbsDownloadManagerRecyclerAdapter.class.getSimpleName(); 33 | 34 | protected final DownloadManager downloadManager; 35 | private final Handler uihandler; 36 | 37 | public AbsDownloadManagerRecyclerAdapter(Context context, RecyclerView mRecyclerView, DownloadManager downloadManager) { 38 | super(context, mRecyclerView); 39 | this.context = context; 40 | this.downloadManager = downloadManager; 41 | uihandler = new Handler(); 42 | } 43 | 44 | @Override 45 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 46 | View view = LayoutInflater.from(context).inflate(R.layout.list_item_download_card, parent, false); 47 | return new ViewHolder(context, view); 48 | } 49 | 50 | @Override 51 | public void onViewRecycled(ViewHolder holder) { 52 | super.onViewRecycled(holder); 53 | holder.unbind(downloadManager); 54 | } 55 | 56 | 57 | public class ViewHolder extends AbsRecyclerViewAdapter.ClickableViewHolder implements DownloadManager.OnDownloadProgressChangeListener { 58 | private final Context context; 59 | 60 | @Bind(R.id.preview) 61 | ImageView mPreview; 62 | @Bind(R.id.title) 63 | TextView mTitle; 64 | @Bind(R.id.download_container) 65 | FrameLayout mDownloadContainer; 66 | @Bind(R.id.progress) 67 | TextView mProgress; 68 | 69 | public Book item; 70 | 71 | public ViewHolder(Context context, View itemView) { 72 | super(itemView); 73 | this.context = context; 74 | ButterKnife.bind(this, itemView); 75 | } 76 | 77 | public void bind(Book item) { 78 | if (item == null) { 79 | Log.e(TAG, "书籍为空"); 80 | return; 81 | } 82 | mTitle.setText(item.name); 83 | if (item.isDownloaded()) { 84 | Picasso.with(AbsDownloadManagerRecyclerAdapter.this.context) 85 | .load(PageApi.getPageFile(AbsDownloadManagerRecyclerAdapter.this.context, item, 0)) 86 | .into(mPreview); 87 | mDownloadContainer.setVisibility(View.GONE); 88 | } else { 89 | TextDrawable drawablePlaceHolder = TextDrawable.builder().buildRect(Utility.getFirstCharacter(item.name), getColor(item)); 90 | Picasso.with(AbsDownloadManagerRecyclerAdapter.this.context) 91 | .load(DouJinMoeUrl.previewUrl(item.token)) 92 | .placeholder(drawablePlaceHolder) 93 | .into(mPreview); 94 | 95 | if (item.isWaitDownload()) { 96 | mProgress.setText(R.string.wait_for_download); 97 | } else if (item.isDownloading()) { 98 | mProgress.setText(context.getResources().getString(R.string.info_total_pages, item.downloadedPosition + 1, item.count)); 99 | } else { 100 | mDownloadContainer.setVisibility(View.GONE); 101 | } 102 | } 103 | 104 | this.item = item; 105 | } 106 | 107 | public void bind(Book book, DownloadManager downloadManager) { 108 | bind(book); 109 | downloadManager.addOnDownloadProgressChangeListener(this); 110 | } 111 | 112 | public void unbind(DownloadManager downloadManager) { 113 | downloadManager.removeOnDownloadProgressChangeListener(this); 114 | } 115 | 116 | private int getColor(Book item) { 117 | return ColorGenerator.MATERIAL.getColor(item.name); 118 | } 119 | 120 | @Override 121 | public void onDownloadProgressChanged(Book book) { 122 | if (book == null || !book.name.equals(item.name)) return; 123 | 124 | //ui Thread 125 | uihandler.post(() -> mProgress.setText(context.getResources().getString(R.string.info_total_pages, book.downloadedPosition + 1, book.count))); 126 | // FIXME: 15/11/28 以下无用删除 127 | // if (book.downloadedPosition + 1 == book.count) { 128 | // uihandler.post(() -> mDownloadContainer.setVisibility(View.GONE)); 129 | // } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/common/AbsActivity.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.common; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.MenuItem; 7 | 8 | import com.fanhl.doujinMoe.App; 9 | import com.fanhl.doujinMoe.R; 10 | import com.fanhl.doujinMoe.model.Book; 11 | import com.fanhl.doujinMoe.util.DownloadManager; 12 | 13 | /** 14 | * Created by fanhl on 15/10/30. 15 | */ 16 | public abstract class AbsActivity extends AppCompatActivity implements DownloadManager.OnDownloadManagerInteractionListener { 17 | 18 | protected App app; 19 | 20 | private DownloadManager downloadManager; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 27 | getWindow().setStatusBarColor(getResources().getColor(R.color.primary)); 28 | getWindow().setNavigationBarColor(getResources().getColor(R.color.primary)); 29 | } 30 | 31 | app = ((App) getApplication()); 32 | downloadManager = app.getDownloadManager(); 33 | } 34 | 35 | @Override 36 | protected void onResume() { 37 | super.onResume(); 38 | downloadManager.registerOnDownloadManagerInteractionListener(this); 39 | } 40 | 41 | @Override 42 | protected void onPause() { 43 | super.onPause(); 44 | downloadManager.unregisterOnDownloadManagerInteractionListener(this); 45 | } 46 | 47 | @Override 48 | public boolean onOptionsItemSelected(MenuItem item) { 49 | if (item.getItemId() == android.R.id.home) { 50 | this.onBackPressed(); 51 | return true; 52 | } 53 | 54 | return super.onOptionsItemSelected(item); 55 | } 56 | 57 | @Override 58 | public void onDMDownloadSuccess(Book book) { 59 | //在需要响应的Activity中实现 60 | } 61 | 62 | @Override 63 | public void onDMDownloadFail(Book book) { 64 | //在需要响应的Activity中实现 65 | } 66 | 67 | public DownloadManager getDownloadManager() { 68 | return downloadManager; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/common/AbsRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.common; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public abstract class AbsRecyclerViewAdapter extends RecyclerView.Adapter { 11 | 12 | protected Context context; 13 | 14 | protected RecyclerView mRecyclerView; 15 | protected List mListeners = new ArrayList<>(); 16 | 17 | public AbsRecyclerViewAdapter(Context context, RecyclerView recyclerView) { 18 | this.context = context; 19 | 20 | this.mRecyclerView = recyclerView; 21 | this.mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 22 | @Override 23 | public void onScrollStateChanged(RecyclerView rv, int newState) { 24 | for (RecyclerView.OnScrollListener listener : mListeners) { 25 | listener.onScrollStateChanged(rv, newState); 26 | } 27 | } 28 | 29 | @Override 30 | public void onScrolled(RecyclerView rv, int dx, int dy) { 31 | for (RecyclerView.OnScrollListener listener : mListeners) { 32 | listener.onScrolled(rv, dx, dy); 33 | } 34 | } 35 | }); 36 | } 37 | 38 | public void addOnScrollListener(RecyclerView.OnScrollListener listener) { 39 | mListeners.add(listener); 40 | } 41 | 42 | public interface OnItemClickListener { 43 | void onItemClick(int position, AbsRecyclerViewAdapter.ClickableViewHolder holder); 44 | } 45 | 46 | public interface OnItemLongClickListener { 47 | boolean onItemLongClick(int position, AbsRecyclerViewAdapter.ClickableViewHolder holder); 48 | } 49 | 50 | private OnItemClickListener itemClickListener; 51 | private OnItemLongClickListener itemLongClickListener; 52 | 53 | public void setOnItemClickListener(OnItemClickListener listener) { 54 | this.itemClickListener = listener; 55 | } 56 | 57 | public void setOnItemLongClickListener(OnItemLongClickListener listener) { 58 | this.itemLongClickListener = listener; 59 | } 60 | 61 | @Override 62 | public void onBindViewHolder(final CVH holder, final int position) { 63 | holder.itemView.setOnClickListener(v -> { 64 | if (itemClickListener != null) { 65 | itemClickListener.onItemClick(position, holder); 66 | } 67 | }); 68 | holder.itemView.setOnLongClickListener(v -> itemLongClickListener != null && itemLongClickListener.onItemLongClick(position, holder)); 69 | } 70 | 71 | public class ClickableViewHolder extends RecyclerView.ViewHolder { 72 | public ClickableViewHolder(View itemView) { 73 | super(itemView); 74 | } 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/AbsFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment; 2 | 3 | import android.app.Fragment; 4 | 5 | import com.fanhl.doujinMoe.App; 6 | import com.fanhl.doujinMoe.ui.common.AbsActivity; 7 | 8 | /** 9 | * Created by fanhl on 15/11/20. 10 | */ 11 | public abstract class AbsFragment extends Fragment { 12 | 13 | public App app() { 14 | return (App) getActivity().getApplication(); 15 | } 16 | 17 | protected AbsActivity getAbsActivity() { 18 | return ((AbsActivity) getActivity()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/BookPageFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment; 2 | 3 | 4 | import android.app.Fragment; 5 | import android.content.res.Configuration; 6 | import android.os.Bundle; 7 | import android.support.v7.widget.AppCompatTextView; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.FrameLayout; 13 | import android.widget.ImageView; 14 | 15 | import com.fanhl.doujinMoe.R; 16 | import com.fanhl.doujinMoe.api.PageApi; 17 | import com.fanhl.doujinMoe.model.Book; 18 | import com.fanhl.doujinMoe.model.Page; 19 | import com.fanhl.doujinMoe.ui.GalleryActivity; 20 | import com.fanhl.photoview.PhotoViewAttacherEx; 21 | import com.fanhl.util.DisplayUtil; 22 | import com.fanhl.util.GsonUtil; 23 | import com.squareup.picasso.Callback; 24 | import com.squareup.picasso.Picasso; 25 | 26 | import butterknife.Bind; 27 | import butterknife.ButterKnife; 28 | import uk.co.senab.photoview.PhotoView; 29 | 30 | /** 31 | * A simple {@link Fragment} subclass. 32 | * Use the {@link BookPageFragment#newInstance} factory method to 33 | * create an instance of this fragment. 34 | */ 35 | public class BookPageFragment extends Fragment { 36 | public static final String TAG = BookPageFragment.class.getSimpleName(); 37 | 38 | private static final String ARG_BOOK_DATA = "ARG_BOOK_DATA"; 39 | private static final String ARG_POSITION = "ARG_POSITION"; 40 | 41 | @Bind(R.id.backgroundView) 42 | FrameLayout mBackgroundView; 43 | @Bind(R.id.textView) 44 | AppCompatTextView mTextView; 45 | @Bind(R.id.imageView) 46 | PhotoView mImageView; 47 | 48 | private Book book; 49 | private int position; 50 | 51 | private PhotoViewAttacherEx mAttacher; 52 | 53 | /** 54 | * @param book 55 | * @param position 56 | * @return 57 | */ 58 | public static BookPageFragment newInstance(Book book, int position) { 59 | BookPageFragment fragment = new BookPageFragment(); 60 | Bundle args = new Bundle(); 61 | args.putString(ARG_BOOK_DATA, GsonUtil.json(book)); 62 | args.putInt(ARG_POSITION, position); 63 | fragment.setArguments(args); 64 | return fragment; 65 | } 66 | 67 | @Override 68 | public void onCreate(Bundle savedInstanceState) { 69 | super.onCreate(savedInstanceState); 70 | Bundle bundle = getArguments(); 71 | book = GsonUtil.obj(bundle.getString(ARG_BOOK_DATA), Book.class); 72 | position = bundle.getInt(ARG_POSITION); 73 | } 74 | 75 | @Override 76 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 77 | Bundle savedInstanceState) { 78 | // Log.d(TAG, "onCreateView page position:" + position); 79 | View view = inflater.inflate(R.layout.fragment_book_page, container, false); 80 | ButterKnife.bind(this, view); 81 | 82 | mBackgroundView.setOnClickListener(view1 -> ((GalleryActivity) getActivity()).toggle()); 83 | mTextView.setText(String.valueOf(position + 1)); 84 | mAttacher = new PhotoViewAttacherEx(mImageView); 85 | mAttacher.setOnViewTapListener((view1, v, v1) -> ((GalleryActivity) getActivity()).toggle()); 86 | 87 | refreshForOrientation(getResources().getConfiguration()); 88 | 89 | if (book.isDownloaded() || PageApi.isPageDownloaded(getActivity(), book, position)) { 90 | Picasso.with(getActivity()) 91 | .load(PageApi.getPageFile(getActivity(), book, position)) 92 | .fit() 93 | .centerInside() 94 | .into(mImageView, new Callback.EmptyCallback() { 95 | @Override 96 | public void onSuccess() { 97 | mAttacher.update(); 98 | } 99 | }); 100 | } else { 101 | Page page = book.pages.get(position); 102 | Picasso.with(getActivity()) 103 | .load(page.href) 104 | .fit() 105 | .centerInside() 106 | .into(mImageView, new Callback.EmptyCallback() { 107 | @Override 108 | public void onSuccess() { 109 | mAttacher.update(); 110 | } 111 | }); 112 | } 113 | 114 | return view; 115 | } 116 | 117 | @Override 118 | public void onDestroyView() { 119 | // Log.d(TAG, "onDestroyView page position:" + position); 120 | super.onDestroyView(); 121 | ButterKnife.unbind(this); 122 | } 123 | 124 | @Override 125 | public void onConfigurationChanged(Configuration newConfig) { 126 | super.onConfigurationChanged(newConfig); 127 | Log.d(TAG, "横竖屏切换"); 128 | refreshForOrientation(newConfig); 129 | } 130 | 131 | private void refreshForOrientation(Configuration newConfig) { 132 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 133 | mAttacher.setScaleType(ImageView.ScaleType.FIT_CENTER); 134 | mAttacher.setParentIsVertical(false); 135 | } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 136 | if (position >= book.position) { 137 | // Log.d(TAG, "refreshForOrientation page position:" + position+ " book.position:"+book.position+" CROP_TOP."); 138 | mAttacher.setScaleType(PhotoViewAttacherEx.ScaleTypeEx.CROP_TOP); 139 | } else { 140 | // Log.d(TAG, "refreshForOrientation page position:" + position+ " book.position:"+book.position+" CROP_BOTTOM."); 141 | mAttacher.setScaleType(PhotoViewAttacherEx.ScaleTypeEx.CROP_BOTTOM); 142 | } 143 | mAttacher.setParentIsVertical(true); 144 | } 145 | } 146 | 147 | private int dp2px(float dp) { 148 | return DisplayUtil.dip2px(getActivity(), dp); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/downloadManager/AbsDownloadManagerFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.downloadManager; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | 6 | import com.fanhl.doujinMoe.interfaceX.OnDownloadManagerBookChangeListener; 7 | import com.fanhl.doujinMoe.ui.DownloadManagerActivity; 8 | import com.fanhl.doujinMoe.ui.fragment.AbsFragment; 9 | 10 | /** 11 | * Created by fanhl on 15/11/27. 12 | */ 13 | public abstract class AbsDownloadManagerFragment extends AbsFragment implements OnDownloadManagerBookChangeListener { 14 | public static final String TAG = AbsDownloadManagerFragment.class.getSimpleName(); 15 | 16 | // FIXME: 15/11/28 到没问题 下面就删了 17 | // /** 18 | // * 这个方法实际没有调用!!!,所以我还是用 onAttach(Activity activity) 了!!? 19 | // * fixme 两个方法都实现的话,会不会重复? 20 | // * @param context 21 | // */ 22 | // @Override 23 | // public void onAttach(Context context) { 24 | // super.onAttach(context); 25 | // Log.d(TAG, "onAttach(context) addOnDownloadManagerBookChangeListener"); 26 | // ((DownloadManagerActivity) context).addOnDownloadManagerBookChangeListener(this); 27 | // } 28 | 29 | @Override 30 | public void onAttach(Activity activity) { 31 | super.onAttach(activity); 32 | Log.d(TAG, "onAttach(activity) addOnDownloadManagerBookChangeListener"); 33 | ((DownloadManagerActivity) activity).addOnDownloadManagerBookChangeListener(this); 34 | } 35 | 36 | @Override 37 | public void onDetach() { 38 | super.onDetach(); 39 | Log.d(TAG, "onDetach removeOnDownloadManagerBookChangeListener"); 40 | ((DownloadManagerActivity) getActivity()).removeOnDownloadManagerBookChangeListener(this); 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/downloadManager/DownloadFailFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.downloadManager; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.support.v7.widget.StaggeredGridLayoutManager; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.fanhl.doujinMoe.R; 14 | import com.fanhl.doujinMoe.model.Book; 15 | import com.fanhl.doujinMoe.ui.DetailsActivity; 16 | import com.fanhl.doujinMoe.ui.adapter.downloadManager.AbsDownloadManagerRecyclerAdapter; 17 | import com.fanhl.doujinMoe.util.DownloadManager; 18 | 19 | import butterknife.Bind; 20 | import butterknife.ButterKnife; 21 | 22 | /** 23 | * Created by fanhl on 15/11/27. 24 | */ 25 | public class DownloadFailFragment extends AbsDownloadManagerFragment { 26 | public static final String TAG = DownloadFailFragment.class.getSimpleName(); 27 | 28 | @Bind(R.id.recycler_view) 29 | RecyclerView mRecyclerView; 30 | 31 | AbsDownloadManagerRecyclerAdapter mAdapter; 32 | private DownloadManager downloadManager; 33 | 34 | public static DownloadFailFragment newInstance() { 35 | return new DownloadFailFragment(); 36 | } 37 | 38 | @Nullable 39 | @Override 40 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 41 | View view = inflater.inflate(R.layout.fragment_recycle, container, false); 42 | ButterKnife.bind(this, view); 43 | assignViews(); 44 | return view; 45 | } 46 | 47 | @Override 48 | public void onDestroyView() { 49 | super.onDestroyView(); 50 | mRecyclerView.setAdapter(null); 51 | ButterKnife.unbind(this); 52 | } 53 | 54 | private void assignViews() { 55 | //流式布局 56 | StaggeredGridLayoutManager mLayoutManager = new StaggeredGridLayoutManager(getResources().getInteger(R.integer.span_count_book), StaggeredGridLayoutManager.VERTICAL); 57 | mRecyclerView.setLayoutManager(mLayoutManager); 58 | mRecyclerView.setHasFixedSize(true); 59 | 60 | //mRecyclerView 61 | downloadManager = getAbsActivity().getDownloadManager(); 62 | mAdapter = new AbsDownloadManagerRecyclerAdapter(getActivity(), mRecyclerView, downloadManager) { 63 | @Override 64 | public void onBindViewHolder(ViewHolder holder, int position) { 65 | super.onBindViewHolder(holder, position); 66 | holder.bind(downloadManager.getFailBooks().get(position)); 67 | } 68 | 69 | @Override 70 | public int getItemCount() { 71 | return downloadManager.getFailBooks().size(); 72 | } 73 | }; 74 | mRecyclerView.setAdapter(mAdapter); 75 | mAdapter.setOnItemClickListener((position, viewHolder) -> DetailsActivity.launch(getActivity(), ((AbsDownloadManagerRecyclerAdapter.ViewHolder) viewHolder).item)); 76 | mAdapter.setOnItemLongClickListener((position, holder) -> { 77 | Log.d(TAG, "加入重新下载确认."); 78 | Snackbar.make(mRecyclerView, R.string.text_retry_download, Snackbar.LENGTH_LONG).setAction(R.string.action_download, v -> { 79 | downloadManager.retryDownload(((AbsDownloadManagerRecyclerAdapter.ViewHolder) holder).item); 80 | }).show(); 81 | return true; 82 | }); 83 | } 84 | 85 | @Override 86 | public void onDownloadBookChanged(Book book, boolean success) { 87 | Log.d(TAG, "onDownloadBookChanged 刷新列表"); 88 | mAdapter.notifyDataSetChanged(); 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/downloadManager/DownloadSuccessFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.downloadManager; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.StaggeredGridLayoutManager; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.fanhl.doujinMoe.R; 13 | import com.fanhl.doujinMoe.model.Book; 14 | import com.fanhl.doujinMoe.ui.DetailsActivity; 15 | import com.fanhl.doujinMoe.ui.adapter.downloadManager.AbsDownloadManagerRecyclerAdapter; 16 | import com.fanhl.doujinMoe.util.DownloadManager; 17 | 18 | import butterknife.Bind; 19 | import butterknife.ButterKnife; 20 | 21 | /** 22 | * Created by fanhl on 15/11/27. 23 | */ 24 | public class DownloadSuccessFragment extends AbsDownloadManagerFragment { 25 | public static final String TAG = DownloadSuccessFragment.class.getSimpleName(); 26 | 27 | @Bind(R.id.recycler_view) 28 | RecyclerView mRecyclerView; 29 | 30 | AbsDownloadManagerRecyclerAdapter mAdapter; 31 | private DownloadManager downloadManager; 32 | 33 | public static DownloadSuccessFragment newInstance() { 34 | return new DownloadSuccessFragment(); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 40 | View view = inflater.inflate(R.layout.fragment_recycle, container, false); 41 | ButterKnife.bind(this, view); 42 | assignViews(); 43 | return view; 44 | } 45 | 46 | @Override 47 | public void onDestroyView() { 48 | super.onDestroyView(); 49 | mRecyclerView.setAdapter(null); 50 | ButterKnife.unbind(this); 51 | } 52 | 53 | private void assignViews() { 54 | //流式布局 55 | StaggeredGridLayoutManager mLayoutManager = new StaggeredGridLayoutManager(getResources().getInteger(R.integer.span_count_book), StaggeredGridLayoutManager.VERTICAL); 56 | mRecyclerView.setLayoutManager(mLayoutManager); 57 | mRecyclerView.setHasFixedSize(true); 58 | 59 | //mRecyclerView 60 | downloadManager = getAbsActivity().getDownloadManager(); 61 | mAdapter = new AbsDownloadManagerRecyclerAdapter(getActivity(), mRecyclerView, downloadManager) { 62 | @Override 63 | public void onBindViewHolder(ViewHolder holder, int position) { 64 | super.onBindViewHolder(holder, position); 65 | holder.bind(downloadManager.getDownloadedBooks().get(position)); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return downloadManager.getDownloadedBooks().size(); 71 | } 72 | }; 73 | mRecyclerView.setAdapter(mAdapter); 74 | mAdapter.setOnItemClickListener((position, viewHolder) -> DetailsActivity.launch(getActivity(), ((AbsDownloadManagerRecyclerAdapter.ViewHolder) viewHolder).item)); 75 | } 76 | 77 | @Override 78 | public void onDownloadBookChanged(Book book, boolean success) { 79 | Log.d(TAG, "onDownloadBookChanged 刷新列表"); 80 | mAdapter.notifyDataSetChanged(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/downloadManager/DownloadingFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.downloadManager; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.support.v7.widget.StaggeredGridLayoutManager; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.fanhl.doujinMoe.R; 14 | import com.fanhl.doujinMoe.model.Book; 15 | import com.fanhl.doujinMoe.ui.DetailsActivity; 16 | import com.fanhl.doujinMoe.ui.adapter.downloadManager.AbsDownloadManagerRecyclerAdapter; 17 | import com.fanhl.doujinMoe.util.DownloadManager; 18 | 19 | import butterknife.Bind; 20 | import butterknife.ButterKnife; 21 | 22 | /** 23 | * Created by fanhl on 15/11/24. 24 | */ 25 | public class DownloadingFragment extends AbsDownloadManagerFragment { 26 | public static final String TAG = DownloadingFragment.class.getSimpleName(); 27 | 28 | // @Bind(R.id.swipe_refresh_layout) 29 | // SwipeRefreshLayout mSwipeRefreshLayout; 30 | @Bind(R.id.recycler_view) 31 | RecyclerView mRecyclerView; 32 | 33 | AbsDownloadManagerRecyclerAdapter mAdapter; 34 | private DownloadManager downloadManager; 35 | 36 | public static DownloadingFragment newInstance() { 37 | return new DownloadingFragment(); 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 43 | View view = inflater.inflate(R.layout.fragment_recycle, container, false); 44 | ButterKnife.bind(this, view); 45 | assignViews(); 46 | return view; 47 | } 48 | 49 | @Override 50 | public void onDestroyView() { 51 | super.onDestroyView(); 52 | mRecyclerView.setAdapter(null); 53 | ButterKnife.unbind(this); 54 | } 55 | 56 | private void assignViews() { 57 | //流式布局 58 | StaggeredGridLayoutManager mLayoutManager = new StaggeredGridLayoutManager(getResources().getInteger(R.integer.span_count_book), StaggeredGridLayoutManager.VERTICAL); 59 | mRecyclerView.setLayoutManager(mLayoutManager); 60 | mRecyclerView.setHasFixedSize(true); 61 | 62 | //mRecyclerView 63 | downloadManager = getAbsActivity().getDownloadManager(); 64 | mAdapter = new AbsDownloadManagerRecyclerAdapter(getActivity(), mRecyclerView, downloadManager) { 65 | @Override 66 | public void onBindViewHolder(ViewHolder holder, int position) { 67 | super.onBindViewHolder(holder, position); 68 | // FIXME: 15/11/27 存在一个正在下载,多个未下载时.当正在下载完成后.再notifyDataChanged后显示不正常. 69 | if (downloadManager.getDownloadingBook() != null) { 70 | if (position == 0) { 71 | holder.bind(downloadManager.getDownloadingBook(), downloadManager); 72 | } else { 73 | holder.bind(downloadManager.getWaitBooks().get(position - 1), downloadManager); 74 | } 75 | } else { 76 | holder.bind(downloadManager.getWaitBooks().get(position), downloadManager); 77 | } 78 | 79 | } 80 | 81 | @Override 82 | public int getItemCount() { 83 | return downloadManager.getWaitBooks().size() + (downloadManager.getDownloadingBook() == null ? 0 : 1); 84 | } 85 | }; 86 | 87 | mRecyclerView.setAdapter(mAdapter); 88 | 89 | mAdapter.setOnItemClickListener((position, viewHolder) -> DetailsActivity.launch(getActivity(), ((AbsDownloadManagerRecyclerAdapter.ViewHolder) viewHolder).item)); 90 | mAdapter.setOnItemLongClickListener((position, holder) -> { 91 | Log.d(TAG, "取消下载确认."); 92 | Snackbar.make(mRecyclerView, R.string.text_cancel_download, Snackbar.LENGTH_LONG).setAction(R.string.action_cancel, v -> { 93 | downloadManager.cancelDownload(((AbsDownloadManagerRecyclerAdapter.ViewHolder) holder).item); 94 | }).show(); 95 | return true; 96 | }); 97 | } 98 | 99 | @Override 100 | public void onDownloadBookChanged(Book book, boolean success) { 101 | Log.d(TAG, "onDownloadBookChanged 刷新列表"); 102 | mAdapter.notifyDataSetChanged(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/AbcFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.fanhl.doujinMoe.R; 6 | 7 | /** 8 | * http://m.doujin-moe.us/abc 9 | * Created by fanhl on 15/12/1. 10 | */ 11 | public class AbcFragment extends AbsHomeFragment { 12 | public static AbcFragment newInstance() { 13 | return new AbcFragment(); 14 | } 15 | 16 | @Override 17 | @NonNull 18 | protected String getToken() { 19 | return "abc"; 20 | } 21 | 22 | /** 23 | * @return 24 | */ 25 | @Override 26 | @NonNull 27 | protected String getSort() { 28 | return "name"; 29 | } 30 | 31 | /** 32 | *

  • Non-series
  • 33 | *
  • 0-9
  • 34 | *
  • A
  • 35 | *
  • A
  • 36 | *
  • B
  • 37 | *
  • ...
  • 38 | *
  • Z
  • 39 | * 40 | * @return 41 | */ 42 | @NonNull 43 | @Override 44 | protected String getParam() { 45 | return "Non-series"; 46 | } 47 | 48 | @Override 49 | protected int getLoadFailMsgResId() { 50 | return R.string.text_abc_get_fail; 51 | } 52 | 53 | // FIXME: 15/12/2 过滤条件 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/AbsBookRecyclerFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.widget.SwipeRefreshLayout; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.support.v7.widget.StaggeredGridLayoutManager; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.fanhl.doujinMoe.R; 14 | import com.fanhl.doujinMoe.model.Book; 15 | import com.fanhl.doujinMoe.ui.DetailsActivity; 16 | import com.fanhl.doujinMoe.ui.adapter.BookGridRecyclerAdapter; 17 | import com.fanhl.doujinMoe.ui.fragment.AbsFragment; 18 | import com.fanhl.loadmore.OnRcvScrollListener; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import butterknife.Bind; 24 | import butterknife.ButterKnife; 25 | 26 | /** 27 | * Created by fanhl on 15/11/10. 28 | */ 29 | public abstract class AbsBookRecyclerFragment extends AbsFragment { 30 | public static final String TAG = AbsBookRecyclerFragment.class.getSimpleName(); 31 | 32 | @Bind(R.id.swipe_refresh_layout) 33 | SwipeRefreshLayout mSwipeRefreshLayout; 34 | @Bind(R.id.recycler_view) 35 | RecyclerView mRecyclerView; 36 | 37 | protected BookGridRecyclerAdapter mAdapter; 38 | protected List mBooks; 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 43 | View view = inflater.inflate(R.layout.fragment_swipe_recycle, container, false); 44 | ButterKnife.bind(this, view); 45 | assignViews(); 46 | initData(); 47 | refreshData(); 48 | return view; 49 | } 50 | 51 | @Override 52 | public void onDestroyView() { 53 | super.onDestroyView(); 54 | ButterKnife.unbind(this); 55 | } 56 | 57 | protected void assignViews() { 58 | mSwipeRefreshLayout.setColorSchemeColors(getResources().getIntArray(R.array.refresh_array)); 59 | mSwipeRefreshLayout.setOnRefreshListener(this::refreshData); 60 | 61 | //流式布局 62 | StaggeredGridLayoutManager mLayoutManager = new StaggeredGridLayoutManager(getResources().getInteger(R.integer.span_count_book), StaggeredGridLayoutManager.VERTICAL); 63 | mRecyclerView.setLayoutManager(mLayoutManager); 64 | mRecyclerView.setHasFixedSize(true); 65 | 66 | //mRecyclerView 67 | mBooks = new ArrayList<>(); 68 | mAdapter = new BookGridRecyclerAdapter(getActivity(), mRecyclerView, mBooks); 69 | mRecyclerView.setAdapter(mAdapter); 70 | 71 | mAdapter.setOnItemClickListener((position, viewHolder) -> { 72 | BookGridRecyclerAdapter.ViewHolder holder = (BookGridRecyclerAdapter.ViewHolder) viewHolder; 73 | DetailsActivity.launch(getActivity(), holder.item); 74 | }); 75 | 76 | mRecyclerView.addOnScrollListener(new OnRcvScrollListener() { 77 | @Override 78 | public void onScrollBottom() { 79 | super.onScrollBottom(); 80 | loadMore(); 81 | } 82 | }); 83 | } 84 | 85 | protected void initData() { 86 | 87 | } 88 | 89 | protected abstract void refreshData(); 90 | 91 | protected void loadMore() { 92 | //load more data 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/AbsHomeFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.design.widget.Snackbar; 5 | import android.util.Log; 6 | 7 | import com.fanhl.doujinMoe.R; 8 | import com.fanhl.doujinMoe.common.Constants; 9 | 10 | import rx.android.schedulers.AndroidSchedulers; 11 | import rx.schedulers.Schedulers; 12 | 13 | /** 14 | * 首页(newest,best,artist...的公共父类 15 | * Created by fanhl on 15/12/1. 16 | */ 17 | public abstract class AbsHomeFragment extends AbsBookRecyclerFragment { 18 | public static final String TAG = AbsHomeFragment.class.getSimpleName(); 19 | 20 | /*是滞已加载完所有数据*/ 21 | private boolean isLoadingComplete = false; 22 | /*loadMore用*/ 23 | private boolean isLoadingData = false; 24 | private int offset = 0; 25 | 26 | 27 | @Override 28 | protected void initData() { 29 | super.initData(); 30 | } 31 | 32 | @Override 33 | protected void refreshData() { 34 | if (!mSwipeRefreshLayout.isRefreshing()) mSwipeRefreshLayout.setRefreshing(true); 35 | offset = 0; 36 | loadData(false); 37 | } 38 | 39 | @Override 40 | protected void loadMore() { 41 | // 到底部自动加载 42 | loadData(true); 43 | } 44 | 45 | private void loadData(boolean isLoadMore) { 46 | if (!isLoadingData && !(isLoadMore && isLoadingComplete)) { 47 | if (!isLoadMore) Log.d(TAG, "refresh data."); 48 | else Log.d(TAG, "loading more."); 49 | 50 | isLoadingData = true; 51 | 52 | app().getClient().getHomeService() 53 | .bookList(getToken(), offset, Constants.PAGE_BOOK_COUNT_MAX, getSort(), getParam()) 54 | .subscribeOn(Schedulers.io()) 55 | .observeOn(AndroidSchedulers.mainThread()) 56 | .subscribe(folderResponse -> { 57 | isLoadingData = false; 58 | isLoadingComplete = folderResponse.complete; 59 | if (isLoadingComplete) Log.d(TAG, "已加载完数据"); 60 | offset += Constants.PAGE_BOOK_COUNT_MAX; 61 | if (mSwipeRefreshLayout == null) return; 62 | mSwipeRefreshLayout.setRefreshing(false); 63 | if (!isLoadMore) mAdapter.clear(); 64 | mAdapter.addItems(folderResponse.folders); 65 | }, throwable -> { 66 | isLoadingData = false; 67 | mSwipeRefreshLayout.setRefreshing(false); 68 | if (mSwipeRefreshLayout == null) return; 69 | Log.e(NewestFragment.TAG, Log.getStackTraceString(throwable)); 70 | Snackbar.make(mSwipeRefreshLayout, getLoadFailMsgResId(), Snackbar.LENGTH_LONG).setAction(R.string.action_retry, v -> refreshData()).show(); 71 | }); 72 | } 73 | } 74 | 75 | /** 76 | * 取得版块类型 77 | *

    78 | *

  • newest
  • 79 | *
  • best
  • 80 | *
  • artist
  • 81 | * 82 | * @return 83 | */ 84 | @NonNull 85 | protected abstract String getToken(); 86 | 87 | /** 88 | * 取得排序方式 89 | *
  • name
  • 90 | *

    91 | *

  • ever
  • 92 | *
  • year
  • 93 | *
  • mouth
  • 94 | * 95 | * @return 96 | */ 97 | @NonNull 98 | protected abstract String getSort(); 99 | 100 | @NonNull 101 | protected String getParam() { 102 | return ""; 103 | } 104 | 105 | protected abstract int getLoadFailMsgResId(); 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/ArtistFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.fanhl.doujinMoe.R; 6 | 7 | /** 8 | * Created by fanhl on 15/11/8. 9 | */ 10 | public class ArtistFragment extends AbsHomeFragment { 11 | 12 | public static ArtistFragment newInstance() { 13 | return new ArtistFragment(); 14 | } 15 | 16 | @Override 17 | @NonNull 18 | protected String getToken() { 19 | return "artist"; 20 | } 21 | 22 | @Override 23 | @NonNull 24 | protected String getSort() { 25 | return "name"; 26 | } 27 | 28 | @Override 29 | protected int getLoadFailMsgResId() { 30 | return R.string.text_artist_get_fail; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/BestFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.fanhl.doujinMoe.R; 6 | 7 | /** 8 | * Top Rated 9 | */ 10 | public class BestFragment extends AbsHomeFragment { 11 | public static BestFragment newInstance() { 12 | return new BestFragment(); 13 | } 14 | 15 | @Override 16 | @NonNull 17 | protected String getToken() { 18 | return "best"; 19 | } 20 | 21 | /** 22 | *
  • ever
  • 23 | *
  • year
  • 24 | *
  • mouth
  • 25 | * 26 | * @return 27 | */ 28 | @Override 29 | @NonNull 30 | protected String getSort() { 31 | return "mouth"; 32 | } 33 | 34 | @Override 35 | protected int getLoadFailMsgResId() { 36 | return R.string.text_best_get_fail; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/CategoryFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.fanhl.doujinMoe.R; 6 | 7 | /** 8 | * http://m.doujin-moe.us/category 9 | * Created by fanhl on 15/12/1. 10 | */ 11 | public class CategoryFragment extends AbsHomeFragment { 12 | public static CategoryFragment newInstance() { 13 | return new CategoryFragment(); 14 | } 15 | 16 | // FIXME: 15/12/1 注,这里点item进去不是书籍,而是另一个列表了 17 | 18 | @Override 19 | @NonNull 20 | protected String getToken() { 21 | return "category"; 22 | } 23 | 24 | /** 25 | * @return 26 | */ 27 | @Override 28 | @NonNull 29 | protected String getSort() { 30 | return "name"; 31 | } 32 | 33 | @Override 34 | protected int getLoadFailMsgResId() { 35 | return R.string.text_category_get_fail; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/DownloadedFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.os.Handler; 4 | import android.support.design.widget.Snackbar; 5 | import android.util.Log; 6 | 7 | import com.fanhl.doujinMoe.R; 8 | import com.fanhl.doujinMoe.api.BookApi; 9 | import com.fanhl.doujinMoe.model.Book; 10 | import com.fanhl.doujinMoe.ui.adapter.BookGridRecyclerAdapter; 11 | import com.fanhl.util.ThreadUtil; 12 | 13 | import java.util.List; 14 | 15 | import rx.Observable; 16 | import rx.android.schedulers.AndroidSchedulers; 17 | import rx.android.schedulers.HandlerScheduler; 18 | 19 | /** 20 | * 已下载 21 | * Created by fanhl on 15/11/20. 22 | */ 23 | public class DownloadedFragment extends AbsBookRecyclerFragment { 24 | public static final String TAG = DownloadedFragment.class.getSimpleName(); 25 | 26 | private Handler downloadedHandler; 27 | 28 | public static DownloadedFragment newInstance() { 29 | return new DownloadedFragment(); 30 | } 31 | 32 | @Override 33 | protected void assignViews() { 34 | super.assignViews(); 35 | 36 | mAdapter.setOnItemLongClickListener((position, viewHolder) -> { 37 | Log.d(TAG, "删除书籍确认."); 38 | BookGridRecyclerAdapter.ViewHolder holder = (BookGridRecyclerAdapter.ViewHolder) viewHolder; 39 | Snackbar.make(mRecyclerView, getResources().getString(R.string.text_book_delete, holder.item.name), Snackbar.LENGTH_LONG).setAction(R.string.action_delete, v -> { 40 | if (BookApi.deleteBook(getActivity(), holder.item)) { 41 | app().getLocalManager().refresh(() -> getActivity().runOnUiThread(this::refreshData)); 42 | } 43 | }).show(); 44 | return true; 45 | }); 46 | } 47 | 48 | @Override 49 | protected void initData() { 50 | super.initData(); 51 | downloadedHandler = ThreadUtil.createBackgroundHandler("已下载用线程"); 52 | } 53 | 54 | @Override 55 | protected void refreshData() { 56 | if (!mSwipeRefreshLayout.isRefreshing()) mSwipeRefreshLayout.setRefreshing(true); 57 | Observable.>create(subscriber -> { 58 | try { 59 | subscriber.onNext(app().getLocalManager().getDownloadedBooks()); 60 | subscriber.onCompleted(); 61 | } catch (Exception e) { 62 | subscriber.onError(e); 63 | } 64 | }).subscribeOn(HandlerScheduler.from(downloadedHandler)) 65 | .observeOn(AndroidSchedulers.mainThread()) 66 | .subscribe(books -> { 67 | mSwipeRefreshLayout.setRefreshing(false); 68 | mBooks.clear(); 69 | mBooks.addAll(books); 70 | mAdapter.notifyDataSetChanged(); 71 | }, throwable -> { 72 | mSwipeRefreshLayout.setRefreshing(false); 73 | Log.e(TAG, Log.getStackTraceString(throwable)); 74 | Snackbar.make(mSwipeRefreshLayout, R.string.text_downloaded_get_fail, Snackbar.LENGTH_LONG).setAction(R.string.action_retry, v -> refreshData()).show(); 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/NewestFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.fanhl.doujinMoe.R; 6 | 7 | /** 8 | * Created by fanhl on 15/11/8. 9 | */ 10 | public class NewestFragment extends AbsHomeFragment { 11 | 12 | public static NewestFragment newInstance() { 13 | return new NewestFragment(); 14 | } 15 | 16 | @Override 17 | @NonNull 18 | protected String getToken() { 19 | return "newest"; 20 | } 21 | 22 | @Override 23 | @NonNull 24 | protected String getSort() { 25 | return "name"; 26 | } 27 | 28 | @Override 29 | protected int getLoadFailMsgResId() { 30 | return R.string.text_newest_get_fail; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/fragment/main/SearchFragment.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.fragment.main; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.fanhl.doujinMoe.R; 6 | 7 | /** 8 | * http://m.doujin-moe.us/search?search=lolicon&sort=-created 9 | *

    10 | * Created by fanhl on 15/12/1. 11 | */ 12 | public class SearchFragment extends AbsHomeFragment { 13 | public static SearchFragment newInstance() { 14 | return new SearchFragment(); 15 | } 16 | 17 | @Override 18 | @NonNull 19 | protected String getToken() { 20 | return "search"; 21 | } 22 | 23 | /** 24 | * @return 25 | */ 26 | @Override 27 | @NonNull 28 | protected String getSort() { 29 | return "name"; 30 | } 31 | 32 | /** 33 | * @return 34 | */ 35 | @NonNull 36 | @Override 37 | protected String getParam() { 38 | return "futanari"; 39 | } 40 | 41 | @Override 42 | protected int getLoadFailMsgResId() { 43 | return R.string.text_search_get_fail; 44 | } 45 | 46 | // FIXME: 15/12/2 过滤条件 47 | } 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/widget/NavigationFooterView.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.fanhl.doujinMoe.util.Utility; 9 | 10 | 11 | public class NavigationFooterView extends View { 12 | 13 | public NavigationFooterView(Context context) { 14 | super(context); 15 | } 16 | 17 | public NavigationFooterView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public NavigationFooterView(Context context, AttributeSet attrs, int defStyleAttr) { 22 | super(context, attrs, defStyleAttr); 23 | } 24 | 25 | @Override 26 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 27 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 28 | 29 | ViewGroup.LayoutParams params = getLayoutParams(); 30 | 31 | if (isInEditMode()) { 32 | params.height = 0; 33 | return; 34 | } 35 | 36 | params.height = Utility.getNavigationBarHeight(getContext()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/ui/widget/StatusBarHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.fanhl.doujinMoe.util.Utility; 9 | 10 | 11 | public class StatusBarHeaderView extends View { 12 | 13 | public StatusBarHeaderView(Context context) { 14 | super(context); 15 | } 16 | 17 | public StatusBarHeaderView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public StatusBarHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { 22 | super(context, attrs, defStyleAttr); 23 | } 24 | 25 | @Override 26 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 27 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 28 | 29 | ViewGroup.LayoutParams params = getLayoutParams(); 30 | 31 | if (isInEditMode()) { 32 | params.height = 0; 33 | return; 34 | } 35 | 36 | params.height = Utility.getStatusBarHeight(getContext()); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/util/ColorGenerator.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | /** 8 | * @author amulya 9 | * @datetime 14 Oct 2014, 5:20 PM 10 | */ 11 | public class ColorGenerator { 12 | public static final String TAG = ColorGenerator.class.getSimpleName(); 13 | 14 | public static ColorGenerator DEFAULT; 15 | 16 | public static ColorGenerator MATERIAL; 17 | 18 | static { 19 | DEFAULT = create(Arrays.asList( 20 | 0xfff16364, 21 | 0xfff58559, 22 | 0xfff9a43e, 23 | 0xffe4c62e, 24 | 0xff67bf74, 25 | 0xff59a2be, 26 | 0xff2093cd, 27 | 0xffad62a7, 28 | 0xff805781 29 | )); 30 | MATERIAL = create(Arrays.asList( 31 | 0xffe57373, 32 | 0xfff06292, 33 | 0xffba68c8, 34 | 0xff9575cd, 35 | 0xff7986cb, 36 | 0xff64b5f6, 37 | 0xff4fc3f7, 38 | 0xff4dd0e1, 39 | 0xff4db6ac, 40 | 0xff81c784, 41 | 0xffaed581, 42 | 0xffff8a65, 43 | 0xffd4e157, 44 | 0xffffd54f, 45 | 0xffffb74d, 46 | 0xffa1887f, 47 | 0xff90a4ae 48 | )); 49 | } 50 | 51 | private final List mColors; 52 | private final Random mRandom; 53 | 54 | public static ColorGenerator create(List colorList) { 55 | return new ColorGenerator(colorList); 56 | } 57 | 58 | private ColorGenerator(List colorList) { 59 | mColors = colorList; 60 | mRandom = new Random(System.currentTimeMillis()); 61 | } 62 | 63 | public int getRandomColor() { 64 | return mColors.get(mRandom.nextInt(mColors.size())); 65 | } 66 | 67 | public int getColor(Object key) { 68 | return mColors.get(Math.abs(key != null ? key.hashCode() : 0) % mColors.size()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.util; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | 12 | /** 13 | * Created by fanhl on 15/11/17. 14 | */ 15 | public class FileUtil { 16 | public static final String TAG = FileUtil.class.getSimpleName(); 17 | 18 | /** 19 | * 仅用于读取小文件(json文件) 20 | * 21 | * @param file 22 | * @return 23 | */ 24 | public static String readFile(File file) { 25 | if (file == null || !file.exists() || !file.isFile()) return null; 26 | 27 | InputStream ins = null; 28 | try { 29 | ins = new FileInputStream(file); 30 | byte b[] = new byte[(int) file.length()]; 31 | ins.read(b); 32 | 33 | return new String(b); 34 | } catch (Exception e) { 35 | Log.d(TAG, "读取文件失败."); 36 | e.printStackTrace(); 37 | } finally { 38 | if (ins != null) { 39 | try { 40 | ins.close(); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | } 46 | 47 | return null; 48 | } 49 | 50 | public static boolean deleteDirectory(File directory) { 51 | if (directory.exists()) { 52 | File[] files = directory.listFiles(); 53 | if (null != files) { 54 | for (File file : files) { 55 | if (file.isDirectory()) { 56 | deleteDirectory(file); 57 | } else { 58 | file.delete(); 59 | } 60 | } 61 | } 62 | } 63 | return directory.delete(); 64 | } 65 | 66 | /** 67 | * 将文本(json小文本) 68 | * 69 | * @param file 70 | * @param text 71 | * @return 72 | */ 73 | static boolean writeFile(File file, String text) { 74 | return writeFile(file, text.getBytes()); 75 | } 76 | 77 | private static boolean writeFile(File file, byte[] bytes) { 78 | OutputStream out = null; 79 | try { 80 | out = new FileOutputStream(file); 81 | out.write(bytes); 82 | } catch (IOException e) { 83 | e.printStackTrace(); 84 | Log.d(TAG, "保存文件失败."); 85 | return false; 86 | } finally { 87 | if (out != null) { 88 | try { 89 | out.close(); 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | } 95 | return true; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/util/FullScreenHelper.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.util; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.os.Build; 6 | import android.view.View; 7 | import android.view.ViewConfiguration; 8 | import android.view.Window; 9 | import android.view.WindowManager; 10 | 11 | @SuppressLint("InlinedApi") 12 | public final class FullScreenHelper implements View.OnSystemUiVisibilityChangeListener { 13 | 14 | private static final String TAG = FullScreenHelper.class.getSimpleName(); 15 | 16 | private final int NOT_FULL_SCREEN_JELLY_BEAN = 17 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 18 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 19 | 20 | private final int FULL_SCREEN_JELLY_BEAN = 21 | NOT_FULL_SCREEN_JELLY_BEAN | 22 | View.SYSTEM_UI_FLAG_LOW_PROFILE | 23 | View.SYSTEM_UI_FLAG_FULLSCREEN; 24 | 25 | private final int FULL_SCREEN_FLAG_JELLY_BEAN = 26 | View.SYSTEM_UI_FLAG_LOW_PROFILE | 27 | View.SYSTEM_UI_FLAG_FULLSCREEN; 28 | 29 | private final int NOT_FULL_SCREEN_KITKAT = 30 | NOT_FULL_SCREEN_JELLY_BEAN | 31 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 32 | 33 | private final int FULL_SCREEN_KITKAT = 34 | NOT_FULL_SCREEN_KITKAT | 35 | View.SYSTEM_UI_FLAG_LOW_PROFILE | 36 | View.SYSTEM_UI_FLAG_FULLSCREEN | 37 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | 38 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; 39 | 40 | private boolean mFullScreen; 41 | private final Activity mActivity; 42 | private final Window mWindow; 43 | private final View mDecorView; 44 | 45 | private boolean mHasNavBar; 46 | 47 | private OnFullScreenBrokenListener mListener; 48 | 49 | private final int TEST_SDK = Build.VERSION.SDK_INT; 50 | 51 | public interface OnFullScreenBrokenListener { 52 | 53 | /** 54 | * FullScreen state should be fullScreen or not, 55 | * but user or system broke it 56 | * 57 | * @param fullScreen support to be 58 | */ 59 | public void onFullScreenBroken(boolean fullScreen); 60 | 61 | } 62 | 63 | public FullScreenHelper(Activity activity) { 64 | mFullScreen = false; 65 | mActivity = activity; 66 | mWindow = mActivity.getWindow(); 67 | mDecorView = mWindow.getDecorView(); 68 | mDecorView.setOnSystemUiVisibilityChangeListener(this); 69 | 70 | String mainKey = Utility.getSystemProperties("qemu.hw.mainkeys"); 71 | int resourceId = activity.getResources().getIdentifier("config_showNavigationBar", "bool", "android"); 72 | if (resourceId != 0) { 73 | mHasNavBar = activity.getResources().getBoolean(resourceId); 74 | // check override flag (see static block) 75 | if ("1".equals(mainKey)) { 76 | mHasNavBar = false; 77 | } else if ("0".equals(mainKey)) { 78 | mHasNavBar = true; 79 | } 80 | } else { 81 | mHasNavBar = !ViewConfiguration.get(activity).hasPermanentMenuKey(); 82 | } 83 | } 84 | 85 | public boolean willHideNavBar() { 86 | if (TEST_SDK >= Build.VERSION_CODES.KITKAT && mHasNavBar) 87 | return true; 88 | else 89 | return false; 90 | } 91 | 92 | @Override 93 | public void onSystemUiVisibilityChange(int visibility) { 94 | if (TEST_SDK >= Build.VERSION_CODES.JELLY_BEAN && 95 | TEST_SDK < Build.VERSION_CODES.KITKAT) { 96 | if ((mFullScreen && visibility != FULL_SCREEN_FLAG_JELLY_BEAN) || 97 | (!mFullScreen && visibility != 0)) { 98 | // User or system change visibility 99 | if (mListener != null) 100 | mListener.onFullScreenBroken(mFullScreen); 101 | } 102 | } 103 | } 104 | 105 | public void setOnFullScreenBrokenListener(OnFullScreenBrokenListener l) { 106 | mListener = l; 107 | } 108 | 109 | public boolean getFullScreen() { 110 | return mFullScreen; 111 | } 112 | 113 | public void setFullScreen(boolean fullScreen) { 114 | mFullScreen = fullScreen; 115 | if (fullScreen) { 116 | if (TEST_SDK < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 117 | // Empty 118 | } else if (TEST_SDK < Build.VERSION_CODES.JELLY_BEAN) { 119 | mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 120 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 121 | 122 | } else if (TEST_SDK < Build.VERSION_CODES.KITKAT) { 123 | mDecorView.setSystemUiVisibility(FULL_SCREEN_JELLY_BEAN); 124 | 125 | } else { 126 | mDecorView.setSystemUiVisibility(FULL_SCREEN_KITKAT); 127 | } 128 | } else { 129 | if (TEST_SDK < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 130 | // Empty 131 | } else if (TEST_SDK < Build.VERSION_CODES.JELLY_BEAN) { 132 | mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 133 | 134 | } else if (TEST_SDK < Build.VERSION_CODES.KITKAT) { 135 | mDecorView.setSystemUiVisibility(NOT_FULL_SCREEN_JELLY_BEAN); 136 | 137 | } else { 138 | mDecorView.setSystemUiVisibility(NOT_FULL_SCREEN_KITKAT); 139 | } 140 | } 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/util/LocalManager.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.util; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.util.Log; 6 | 7 | import com.fanhl.doujinMoe.api.BookApi; 8 | import com.fanhl.doujinMoe.exception.GetBookFailException; 9 | import com.fanhl.doujinMoe.model.Book; 10 | import com.fanhl.util.ThreadUtil; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import rx.Observable; 16 | import rx.android.schedulers.HandlerScheduler; 17 | 18 | /** 19 | * 本地管理(最近阅读,喜爱,已下载) 20 | * Created by fanhl on 15/11/20. 21 | */ 22 | public class LocalManager { 23 | public static final String TAG = LocalManager.class.getSimpleName(); 24 | 25 | private static LocalManager mInstance; 26 | 27 | private final Handler localHandler; 28 | 29 | private final Context context; 30 | 31 | List recentBooks; 32 | List loveBooks; 33 | List downloadedBooks; 34 | 35 | public static LocalManager getInstance(Context context) { 36 | if (mInstance == null) { 37 | mInstance = new LocalManager(context); 38 | } 39 | 40 | return mInstance; 41 | } 42 | 43 | private LocalManager(Context context) { 44 | localHandler = ThreadUtil.createBackgroundHandler("DownloadThread"); 45 | 46 | this.context = context; 47 | 48 | recentBooks = new ArrayList<>(); 49 | loveBooks = new ArrayList<>(); 50 | downloadedBooks = new ArrayList<>(); 51 | 52 | refresh(); 53 | } 54 | 55 | public void refresh() { 56 | refresh(null); 57 | } 58 | 59 | public void refresh(Runnable callback) { 60 | Observable.>create(subscriber -> { 61 | recentBooks.clear(); 62 | loveBooks.clear(); 63 | downloadedBooks.clear(); 64 | try { 65 | subscriber.onNext(BookApi.getLocalBooks(context)); 66 | subscriber.onCompleted(); 67 | } catch (GetBookFailException e) { 68 | subscriber.onError(e); 69 | } 70 | }).subscribeOn(HandlerScheduler.from(localHandler)) 71 | .flatMap(Observable::from) 72 | .subscribe(book -> { 73 | if (book.recent != null) { 74 | recentBooks.add(book); 75 | } 76 | if (book.isDownloaded()) { 77 | downloadedBooks.add(book); 78 | } 79 | }, throwable -> Log.e(TAG, Log.getStackTraceString(throwable)), () -> { 80 | // FIXME: 15/11/20 recentBooks.sort desc 81 | 82 | if (callback != null) callback.run(); 83 | }); 84 | } 85 | 86 | public List getRecentBooks() { 87 | return recentBooks; 88 | } 89 | 90 | public List getLoveBooks() { 91 | return loveBooks; 92 | } 93 | 94 | public List getDownloadedBooks() { 95 | return downloadedBooks; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/util/NumberUtil.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.util; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.text.DecimalFormat; 6 | import java.text.NumberFormat; 7 | 8 | /** 9 | * Created by fanhl on 15/11/20. 10 | */ 11 | public class NumberUtil { 12 | @NonNull 13 | public static String formatPrefix(int index) { 14 | NumberFormat nf = new DecimalFormat("0000"); 15 | return nf.format(index + 1); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/doujinMoe/util/Utility.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.doujinMoe.util; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.util.DisplayMetrics; 6 | import android.view.Display; 7 | import android.view.WindowManager; 8 | 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.lang.reflect.Method; 15 | 16 | public class Utility { 17 | 18 | public static boolean isChrome() { 19 | return Build.BRAND.equals("chromium") || Build.BRAND.equals("chrome"); 20 | } 21 | 22 | public static int getStatusBarHeight(Context context) { 23 | int result = 0; 24 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 25 | if (resourceId > 0) { 26 | result = context.getResources().getDimensionPixelSize(resourceId); 27 | } 28 | return result; 29 | } 30 | 31 | public static String getFirstCharacter(String sentence) { 32 | for (int i = 0; i < sentence.length(); i++) { 33 | String s = sentence.substring(i, i+1); 34 | if (s.equals("[") || s.equals("]")) continue; 35 | if (s.equals("{") || s.equals("}")) continue; 36 | if (s.equals("(") || s.equals(")")) continue; 37 | if (s.equals(",") || s.equals(".")) continue; 38 | if (s.equals("<") || s.equals(">")) continue; 39 | if (s.equals("《") || s.equals("》")) continue; 40 | if (s.equals("【") || s.equals("】")) continue; 41 | if (s.equals("{") || s.equals("}")) continue; 42 | return s; 43 | } 44 | return null; 45 | } 46 | 47 | public static String getSystemProperties(String key) { 48 | try { 49 | Class c = Class.forName("android.os.SystemProperties"); 50 | Method m = c.getDeclaredMethod("get", String.class); 51 | m.setAccessible(true); 52 | return (String) m.invoke(null, key); 53 | } catch (Throwable e) { 54 | return ""; 55 | } 56 | } 57 | 58 | public static int getTrueScreenHeight(Context context) { 59 | int dpi = 0; 60 | Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 61 | DisplayMetrics dm = new DisplayMetrics(); 62 | if (Build.VERSION.SDK_INT >= 17) { 63 | display.getRealMetrics(dm); 64 | dpi = dm.heightPixels; 65 | } else { 66 | try { 67 | Class c = Class.forName("android.view.Display"); 68 | Method method = c.getMethod("getRealMetrics", DisplayMetrics.class); 69 | method.invoke(display, dm); 70 | dpi = dm.heightPixels; 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } 74 | } 75 | 76 | return dpi; 77 | } 78 | 79 | public static int getNavigationBarHeight(Context context) { 80 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 81 | DisplayMetrics dm = new DisplayMetrics(); 82 | wm.getDefaultDisplay().getMetrics(dm); 83 | 84 | return getTrueScreenHeight(context) - dm.heightPixels; 85 | } 86 | 87 | public static void saveStringToFile(Context context, String name, String text) throws IOException { 88 | FileOutputStream fos = context.openFileOutput(name, Context.MODE_PRIVATE); 89 | fos.write(text.getBytes()); 90 | fos.close(); 91 | } 92 | 93 | public static String readStringFromFile(Context context, String name) throws IOException{ 94 | File file = context.getFileStreamPath(name); 95 | InputStream is = new FileInputStream(file); 96 | 97 | byte b[] = new byte[(int) file.length()]; 98 | 99 | is.read(b); 100 | is.close(); 101 | 102 | String string = new String(b); 103 | 104 | return string; 105 | } 106 | 107 | public static int calcProgress(int progress, int max) { 108 | return (int) (((float) progress)/((float) max) * 100); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/loadmore/OnBottomListener.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.loadmore; 2 | 3 | /** 4 | * @author Jack Tony 5 | * @brief 6 | * @date 2015/4/6 7 | */ 8 | public interface OnBottomListener { 9 | public void onScrollBottom(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/loadmore/OnRcvScrollListener.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.loadmore; 2 | 3 | /** 4 | * Created by fanhl on 15/12/1. 5 | */ 6 | 7 | import android.support.v7.widget.GridLayoutManager; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.support.v7.widget.StaggeredGridLayoutManager; 11 | 12 | /** 13 | * @author Jack Tony 14 | * @brief recyle view 滚动监听器 15 | * @date 2015/4/6 16 | */ 17 | public class OnRcvScrollListener extends RecyclerView.OnScrollListener implements OnBottomListener { 18 | 19 | private String TAG = getClass().getSimpleName(); 20 | 21 | public static enum LAYOUT_MANAGER_TYPE { 22 | LINEAR, 23 | GRID, 24 | STAGGERED_GRID 25 | } 26 | 27 | /** 28 | * layoutManager的类型(枚举) 29 | */ 30 | protected LAYOUT_MANAGER_TYPE layoutManagerType; 31 | 32 | /** 33 | * 最后一个的位置 34 | */ 35 | private int[] lastPositions; 36 | 37 | /** 38 | * 最后一个可见的item的位置 39 | */ 40 | private int lastVisibleItemPosition; 41 | /* *//** 42 | * 是否正在加载 43 | *//* 44 | private boolean isLoadingMore = false;*/ 45 | 46 | /** 47 | * 当前滑动的状态 48 | */ 49 | private int currentScrollState = 0; 50 | 51 | @Override 52 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 53 | super.onScrolled(recyclerView, dx, dy); 54 | 55 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 56 | // int lastVisibleItemPosition = -1; 57 | if (layoutManagerType == null) { 58 | if (layoutManager instanceof LinearLayoutManager) { 59 | layoutManagerType = LAYOUT_MANAGER_TYPE.LINEAR; 60 | } else if (layoutManager instanceof GridLayoutManager) { 61 | layoutManagerType = LAYOUT_MANAGER_TYPE.GRID; 62 | } else if (layoutManager instanceof StaggeredGridLayoutManager) { 63 | layoutManagerType = LAYOUT_MANAGER_TYPE.STAGGERED_GRID; 64 | } else { 65 | throw new RuntimeException( 66 | "Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager"); 67 | } 68 | } 69 | 70 | switch (layoutManagerType) { 71 | case LINEAR: 72 | lastVisibleItemPosition = ((LinearLayoutManager) layoutManager) 73 | .findLastVisibleItemPosition(); 74 | break; 75 | case GRID: 76 | lastVisibleItemPosition = ((GridLayoutManager) layoutManager) 77 | .findLastVisibleItemPosition(); 78 | break; 79 | case STAGGERED_GRID: 80 | StaggeredGridLayoutManager staggeredGridLayoutManager 81 | = (StaggeredGridLayoutManager) layoutManager; 82 | if (lastPositions == null) { 83 | lastPositions = new int[staggeredGridLayoutManager.getSpanCount()]; 84 | } 85 | staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions); 86 | lastVisibleItemPosition = findMax(lastPositions); 87 | break; 88 | } 89 | 90 | } 91 | 92 | @Override 93 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 94 | super.onScrollStateChanged(recyclerView, newState); 95 | currentScrollState = newState; 96 | RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 97 | int visibleItemCount = layoutManager.getChildCount(); 98 | int totalItemCount = layoutManager.getItemCount(); 99 | if ((visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE && 100 | (lastVisibleItemPosition) >= totalItemCount - 1)) { 101 | //Log.d(TAG, "is loading more"); 102 | onScrollBottom(); 103 | } 104 | } 105 | 106 | 107 | @Override 108 | public void onScrollBottom() { 109 | //Log.d(TAG, "is onScrollBottom"); 110 | } 111 | 112 | private int findMax(int[] lastPositions) { 113 | int max = lastPositions[0]; 114 | for (int value : lastPositions) { 115 | if (value > max) { 116 | max = value; 117 | } 118 | } 119 | return max; 120 | } 121 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/util/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.util; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * dp、sp 转换为 px 的工具类 7 | * 8 | * @author fxsky 2012.11.12 9 | */ 10 | public class DisplayUtil { 11 | /** 12 | * 将px值转换为dip或dp值,保证尺寸大小不变(有精度损失) 13 | */ 14 | public static int px2dip(Context context, float pxValue) { 15 | final float scale = context.getResources().getDisplayMetrics().density; 16 | return (int) (pxValue / scale + 0.5f); 17 | } 18 | 19 | /** 20 | * 将px值转换为dip或dp值,保证尺寸大小不变(无精度损失) 21 | */ 22 | public static float px2dipByFloat(Context context, float pxValue) { 23 | final float scale = context.getResources().getDisplayMetrics().density; 24 | return (pxValue / scale); 25 | } 26 | 27 | /** 28 | * 将dip或dp值转换为px值,保证尺寸大小不变(有精度损失) 29 | */ 30 | public static int dip2px(Context context, float dipValue) { 31 | final float scale = context.getResources().getDisplayMetrics().density; 32 | return (int) (dipValue * scale + 0.5f); 33 | } 34 | 35 | /** 36 | * 将dip或dp值转换为px值,保证尺寸大小不变(无精度损失) 37 | */ 38 | public static float dip2pxByFloat(Context context, float dipValue) { 39 | final float scale = context.getResources().getDisplayMetrics().density; 40 | return (dipValue * scale); 41 | } 42 | 43 | /** 44 | * 将px值转换为sp值,保证文字大小不变 45 | */ 46 | public static int px2sp(Context context, float pxValue) { 47 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 48 | return (int) (pxValue / fontScale + 0.5f); 49 | } 50 | 51 | /** 52 | * 将sp值转换为px值,保证文字大小不变 53 | */ 54 | public static int sp2px(Context context, float spValue) { 55 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 56 | return (int) (spValue * fontScale + 0.5f); 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/util/GsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.util; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.ExclusionStrategy; 6 | import com.google.gson.FieldAttributes; 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import com.google.gson.JsonSyntaxException; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * Gson用工具类
    15 | * Created by honglin on 2014/9/24. 16 | */ 17 | public class GsonUtil { 18 | public static final String TAG = GsonUtil.class.getSimpleName(); 19 | 20 | private static final Gson gson; 21 | 22 | static { 23 | gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { 24 | /** 25 | * 设置要过滤的属性 26 | */ 27 | @Override 28 | public boolean shouldSkipField(FieldAttributes attr) { 29 | // 设置对应类中要过虑的属性 30 | // if (attr.getDeclaringClass() == UserInfo.class) { 31 | // return "password".equals(attr.getName()); 32 | // } 33 | return false; 34 | } 35 | 36 | /** 37 | * 设置要过滤的类 38 | */ 39 | @Override 40 | public boolean shouldSkipClass(Class clazz) { 41 | // 这里,如果返回true就表示此类要过滤,否则就输出 42 | return false; 43 | } 44 | }).create(); 45 | } 46 | 47 | public static final String json(Object obj) { 48 | return gson.toJson(obj); 49 | } 50 | 51 | public static final T obj(String json, Class classOfT) { 52 | try { 53 | return gson.fromJson(json, classOfT); 54 | } catch (JsonSyntaxException e) { 55 | Log.e(TAG, "解析json失败"); 56 | } 57 | return null; 58 | } 59 | 60 | public static final T obj(String json, Type typeOfT) { 61 | try { 62 | return gson.fromJson(json, typeOfT); 63 | } catch (JsonSyntaxException e) { 64 | Log.e(TAG, "解析json失败"); 65 | } 66 | return null; 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/util/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.util; 2 | 3 | import android.os.Handler; 4 | import android.os.HandlerThread; 5 | import android.support.annotation.NonNull; 6 | 7 | import static android.os.Process.THREAD_PRIORITY_BACKGROUND; 8 | 9 | /** 10 | * Created by fanhl on 15/11/20. 11 | */ 12 | public class ThreadUtil { 13 | /** 14 | * 创建后台线程,Rx用 15 | * 16 | * @param handlerThreadName 17 | * @return 18 | */ 19 | @NonNull 20 | public static Handler createBackgroundHandler(String handlerThreadName) { 21 | HandlerThread downloadThread = new HandlerThread(handlerThreadName, THREAD_PRIORITY_BACKGROUND); 22 | downloadThread.start(); 23 | return new Handler(downloadThread.getLooper()); 24 | } 25 | 26 | public static void sleep(int time) { 27 | try { 28 | Thread.sleep(time); 29 | } catch (InterruptedException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/fanhl/util/UrlUtils.java: -------------------------------------------------------------------------------- 1 | package com.fanhl.util; 2 | 3 | 4 | import java.io.UnsupportedEncodingException; 5 | import java.net.URLEncoder; 6 | 7 | 8 | /** 9 | * @author fanhl 10 | * @date 2014-9-17 下午3:52:28 11 | */ 12 | public class UrlUtils { 13 | public static final String UTF_8 = "UTF-8"; 14 | 15 | public static String urlEncode(String s) { 16 | return urlEncode(s, UTF_8); 17 | } 18 | 19 | public static String[] urlEncode(String... ss) { 20 | if (ss == null) { 21 | return null; 22 | } 23 | 24 | String[] resultStrings = new String[ss.length]; 25 | 26 | for (int i = 0; i < ss.length; i++) { 27 | resultStrings[i] = urlEncode(ss[i], UTF_8); 28 | } 29 | 30 | return resultStrings; 31 | } 32 | 33 | public static String urlEncode(String s, String charsetName) { 34 | String result = null; 35 | try { 36 | result = URLEncoder.encode(s, charsetName); 37 | } catch (UnsupportedEncodingException e) { 38 | e.printStackTrace(); 39 | } 40 | return result; 41 | } 42 | 43 | /** 44 | * 将url与params拼在一起 45 | * 46 | * @param url 47 | * @param values 48 | * @return 49 | */ 50 | public static String getUrlWithParams(String url, String... values) { 51 | //add fanhl 20150819 52 | for (int i = 0; i < values.length; i++) { 53 | if (values[i] == null) { 54 | values[i] = "";//这里是为了防止 null时抛异常 55 | } 56 | } 57 | 58 | return String.format(url, (Object[]) urlEncode(values)); 59 | // return String.format(url, (Object[]) values); 60 | } 61 | 62 | // /** 63 | // * 将url与params拼在一起 64 | // * 65 | // * @param url 66 | // * @param nameValuePairs 67 | // * @return 68 | // */ 69 | // public static String getUrlWithParams(String url, NameValuePair... nameValuePairs) { 70 | // // 加参数 71 | // StringBuilder sb = new StringBuilder(); 72 | // sb.append(url); 73 | // if (nameValuePairs != null && nameValuePairs.length > 0) { 74 | // 75 | // // 若url已有?就不再加了 76 | // boolean hasQuestionMark; 77 | // if (hasQuestionMark = (url.indexOf('?') < 0)) { 78 | // sb.append("?"); 79 | // } 80 | // for (int i = 0; i < nameValuePairs.length; i++) { 81 | // if (i > 0 || hasQuestionMark) { 82 | // sb.append("&"); 83 | // } 84 | // // FIXME 以后确认是否需要encode 85 | // sb.append(String.format("%s=%s", nameValuePairs[i].getName(), 86 | // nameValuePairs[i].getValue())); 87 | // } 88 | // } 89 | // return sb.toString(); 90 | // } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/res/anim/card_slide_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 11 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/anim/state_anim_raise.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/fa_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/fa_book.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/fa_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/fa_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/fa_bookmark_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/fa_bookmark_o.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/fa_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/fa_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/fa_download_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/fa_download_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/place_holder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-hdpi/place_holder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/fa_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/fa_book.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/fa_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/fa_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/fa_bookmark_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/fa_bookmark_o.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/fa_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/fa_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/fa_download_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/fa_download_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/place_holder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-mdpi/place_holder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/fa_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/fa_book.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/fa_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/fa_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/fa_bookmark_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/fa_bookmark_o.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/fa_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/fa_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/fa_download_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/fa_download_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/place_holder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xhdpi/place_holder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fa_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/fa_book.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fa_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/fa_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fa_bookmark_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/fa_bookmark_o.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fa_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/fa_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/fa_download_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/fa_download_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/place_holder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxhdpi/place_holder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/fa_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/fa_book.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/fa_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/fa_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/fa_bookmark_o.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/fa_bookmark_o.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/fa_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/fa_download.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/fa_download_done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/fa_download_done.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/place_holder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/drawable-xxxhdpi/place_holder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v21/list_item_book_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v21/list_item_download_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout-v21/list_item_page_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 25 | 26 | 27 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_download_manager.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | 35 | 40 | 41 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_gallery.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 26 | 27 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 50 | 51 | 56 | 57 | 64 | 65 | 74 | 75 | 76 | 77 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_book_page.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_recycle.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_swipe_recycle.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_book_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_book_card_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 29 | 30 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_download_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_download_card_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 22 | 32 | 33 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_page_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_page_card_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 27 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 |

    3 | 4 | 5 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 34 | 35 | 36 | 37 | 41 | 45 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_details.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_download_manager.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyguests/Doujin-moe-Android/84125a500c31ff30213108a1c4e722e5f09d43e9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 64dp 7 | 64dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #E91E63 4 | #C2185B 5 | #F8BBD0 6 | #FF9800 7 | #212121 8 | #727272 9 | #FFFFFF 10 | #B6B6B6 11 | 12 | 13 | 14 | 15 | 16 | #BA68C8 17 | #E91E63 18 | #3F51B5 19 | #2196F3 20 | #009688 21 | #4CAF50 22 | #81C784 23 | #FF9800 24 | #795548 25 | 26 | @color/purple_300 27 | @color/pink_500 28 | @color/indigo_500 29 | @color/blue_500 30 | @color/teal_500 31 | @color/green_500 32 | @color/green_300 33 | @color/orange_500 34 | @color/brown_500 35 | 36 | 37 | #7f212121 38 | 39 | #6FE91E63 40 | #4b4b4b 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 160dp 5 | 6 | 0dp 7 | 0dp 8 | 16dp 9 | 16dp 10 | 16dp 11 | 12 | 6dp 13 | 14 | 5dp 15 | 360dp 16 | 16dp 17 | 100sp 18 | 70dp 19 | 8dp 20 | 21 | 120dp 22 | 1600dp 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/drawables.xml: -------------------------------------------------------------------------------- 1 | 2 | @android:drawable/ic_menu_camera 3 | @android:drawable/ic_menu_gallery 4 | @android:drawable/ic_menu_slideshow 5 | @android:drawable/ic_menu_manage 6 | @android:drawable/ic_menu_share 7 | @android:drawable/ic_menu_send 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/integer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 5 | 2 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Doujin-moe 3 | 4 | Open navigation drawer 5 | Close navigation drawer 6 | 7 | Settings 8 | 重试 9 | 下载 10 | 查看 11 | 12 | 最新 13 | 排行榜 14 | 作者 15 | 已下载 16 | 取得 最新漫画 失败. 17 | 取得 排行榜 失败. 18 | 取得 作者版块 失败. 19 | 取得 类别版块 失败. 20 | 取得 名称版块 失败. 21 | 取得 检索版块 失败. 22 | 取得 已下载书籍 失败. 23 | 书籍详细 24 | 刷新数据失败. 25 | 图库 26 | %1$d/%2$d 27 | 书籍 %s 下载成功. 28 | 书籍 %s 下载失败. 29 | 书签 30 | 下载管理 31 | Hello World from section: %1$d 32 | 下载管理 33 | 等待下载 34 | 下载中 35 | 下载完成 36 | 下载失败 37 | 是否取消下载? 38 | 取消 39 | 是否重新下载? 40 | 是否删除书籍 %s . 41 | 删除 42 | 类别 43 | 名称 44 | 检索 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 |