├── .gitignore ├── Daily.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── libs │ └── universal-image-loader-1.9.3-with-sources.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── aspsine │ │ └── zhihu │ │ └── daily │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── aspsine │ │ └── zhihu │ │ └── daily │ │ ├── App.java │ │ ├── Constants.java │ │ ├── CrashHandler.java │ │ ├── animation │ │ └── AnimationEndListener.java │ │ ├── api │ │ ├── DailyApi.java │ │ ├── DailyApiService.java │ │ └── RestApi.java │ │ ├── db │ │ ├── CacheDao.java │ │ ├── DBOpenHelper.java │ │ └── SQLUtils.java │ │ ├── interfaces │ │ └── NavigationDrawerCallbacks.java │ │ ├── model │ │ ├── Cache.java │ │ ├── DailyStories.java │ │ ├── Editor.java │ │ ├── StartImage.java │ │ ├── Story.java │ │ ├── Theme.java │ │ └── Themes.java │ │ ├── network │ │ ├── BaseHttp.java │ │ ├── BaseHttpClient.java │ │ ├── CacheInterceptor.java │ │ ├── Http.java │ │ └── OkHttp.java │ │ ├── respository │ │ ├── CacheRepositoryImpl.java │ │ ├── NetRepositoryImpl.java │ │ ├── RepositoryImpl.java │ │ └── interfaces │ │ │ ├── CacheRepository.java │ │ │ ├── NetRepository.java │ │ │ └── Repository.java │ │ ├── ui │ │ ├── activity │ │ │ ├── BaseAppCompatActivity.java │ │ │ ├── GuiderActivity.java │ │ │ ├── NavigationDrawerActivity.java │ │ │ ├── SettingsActivity.java │ │ │ └── StoryActivity.java │ │ ├── adapter │ │ │ ├── DailyStoriesAdapter.java │ │ │ ├── NavigationDrawerAdapter.java │ │ │ ├── ThemeStoriesAdapter.java │ │ │ └── holder │ │ │ │ ├── DateViewHolder.java │ │ │ │ ├── HeaderViewPagerHolder.java │ │ │ │ ├── RecyclerHeaderViewHolder.java │ │ │ │ └── StoryViewHolder.java │ │ ├── fragment │ │ │ ├── BaseFragment.java │ │ │ ├── DailyStoriesFragment.java │ │ │ ├── GuideFragment.java │ │ │ ├── NavigationFragment.java │ │ │ ├── SettingsFragment.java │ │ │ ├── SplashFragment.java │ │ │ ├── StoryFragment.java │ │ │ └── ThemeStoriesFragment.java │ │ └── widget │ │ │ ├── AvatarsView.java │ │ │ ├── CheckableLinearLayout.java │ │ │ ├── CircleBitmapDisplayer.java │ │ │ ├── CirclePageIndicator.java │ │ │ ├── LoadMoreRecyclerView.java │ │ │ ├── MySwipeRefreshLayout.java │ │ │ ├── MyViewPager.java │ │ │ ├── PageIndicator.java │ │ │ └── StoryHeaderView.java │ │ └── util │ │ ├── DateUtils.java │ │ ├── DensityUtil.java │ │ ├── FileUtils.java │ │ ├── IntentUtils.java │ │ ├── L.java │ │ ├── ListUtils.java │ │ ├── NetWorkUtils.java │ │ ├── ScrollPullDownHelper.java │ │ ├── SharedPrefUtils.java │ │ ├── UIUtils.java │ │ └── WebUtils.java │ └── res │ ├── anim │ └── splash.xml │ ├── drawable-hdpi │ ├── comment_avatar.png │ ├── dark_comment_avatar.png │ ├── drawer_shadow.9.png │ ├── home_pic.png │ ├── ic_launcher.png │ ├── ic_logo.png │ ├── menu_home.png │ └── splash_logo.png │ ├── drawable-mdpi │ ├── comment_avatar.png │ ├── dark_comment_avatar.png │ ├── drawer_shadow.9.png │ ├── home_pic.png │ ├── ic_launcher.png │ ├── ic_logo.png │ ├── menu_home.png │ └── splash_logo.png │ ├── drawable-xhdpi │ ├── comment_avatar.png │ ├── dark_comment_avatar.png │ ├── drawer_shadow.9.png │ ├── home_pic.png │ ├── ic_launcher.png │ ├── ic_logo.png │ ├── menu_home.png │ └── splash_logo.png │ ├── drawable-xxhdpi │ ├── comment_avatar.png │ ├── dark_comment_avatar.png │ ├── drawer_shadow.9.png │ ├── home_pic.png │ ├── ic_launcher.png │ ├── ic_logo.png │ ├── menu_home.png │ └── splash_logo.png │ ├── drawable-xxxhdpi │ └── ic_launcher.png │ ├── drawable │ ├── bg_splash.jpg │ ├── circle_mask.xml │ ├── dark_image_small_default.png │ ├── image_small_default.png │ ├── mask.xml │ └── selected_navdrawer_item_background.xml │ ├── layout │ ├── activity_guider.xml │ ├── activity_navigation_drawer.xml │ ├── activity_settings.xml │ ├── activity_story.xml │ ├── fragment_daily_stories.xml │ ├── fragment_guide.xml │ ├── fragment_navigation.xml │ ├── fragment_settings.xml │ ├── fragment_splash.xml │ ├── fragment_story.xml │ ├── fragment_theme_stories.xml │ ├── layout_actionbar_toolbar.xml │ ├── layout_avatars.xml │ ├── nav_drawer_header.xml │ ├── nav_drawer_item.xml │ ├── nav_drawer_separator.xml │ ├── recycler_header.xml │ ├── recycler_header_viewpager.xml │ ├── recycler_item_date.xml │ ├── recycler_item_story.xml │ ├── view_avatars.xml │ └── view_header_story.xml │ ├── menu │ ├── global.xml │ ├── menu_guider.xml │ ├── menu_navigation_drawer.xml │ └── menu_story.xml │ ├── values-v19 │ └── styles.xml │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── attrs.xml │ ├── colors.xml │ ├── default.xml │ ├── dimens.xml │ ├── strings.xml │ ├── strings_activity_settings.xml │ └── styles.xml │ └── xml │ ├── pref_data_sync.xml │ ├── pref_general.xml │ ├── pref_headers.xml │ └── pref_notification.xml ├── art ├── daily.apk ├── detail.png ├── drawer.png ├── main.png ├── section.png └── splash.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | .DS_Store 5 | /build 6 | -------------------------------------------------------------------------------- /Daily.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Daily 2 | A demo app of Zhihu Daily with full Material Design and new widgets. It's more lighter(less than 2mb) and smarter(no ad, no account) than official app. 3 | 4 | ### Download 5 | [download](https://raw.githubusercontent.com/Aspsine/Daily/master/art/daily.apk) 6 | 7 | ### Features 8 | - Full Material Design 9 | - Android Compat Theme 10 | - RecyclerView 11 | - Toolbar 12 | - SwipeRefreshLayout 13 | - CardView 14 | - DrawerLayout 15 | - ActionBarDrawerToggle 16 | - Animations 17 | - RESTful API Design 18 | 19 | ### Libraris: 20 | - com.android.support:support-v4:22.1.0 21 | - com.android.support:appcompat-v7:22.1.0 22 | - com.android.support:recyclerview-v7:22.1.0 23 | - com.android.support:cardview-v7:22.1.0 24 | - com.google.code.gson:gson:2.3.1 25 | - com.jakewharton:butterknife:6.0.0 26 | - com.squareup.retrofit:retrofit:1.9.0 27 | - com.squareup.okhttp:okhttp:2.3.0 28 | 29 | ### Version 30 | - 1.0 Beta 31 | 32 | ### TODO List 33 | - Actionbar date change(Done) 34 | - Item read status(set color grey) 35 | - Add News section List(Done) 36 | - Off-line data cache(Done) 37 | - Settings 38 | - MVC -> MVP project Refactoring 39 | - Network interface encryption 40 | - Proguard 41 | 42 | ### ScreenShot 43 | ![splash](https://raw.githubusercontent.com/Aspsine/Daily/master/art/splash.png) 44 | ![main](https://raw.githubusercontent.com/Aspsine/Daily/master/art/main.png) 45 | ![drawer](https://raw.githubusercontent.com/Aspsine/Daily/master/art/drawer.png) 46 | ![section](https://raw.githubusercontent.com/Aspsine/Daily/master/art/section.png) 47 | ![detail](https://raw.githubusercontent.com/Aspsine/Daily/master/art/detail.png) 48 | 49 | ### Contact Me 50 | - Github: github.com/aspsine 51 | - Email: littleximail@gmail.com 52 | - Linkedin: cn.linkedin.com/in/aspsine 53 | 54 | ### License 55 | 56 | Copyright 2015 Zhihu Inc. All rights reserved. 57 | 58 | Licensed under the Apache License, Version 2.0 (the "License"); 59 | you may not use this file except in compliance with the License. 60 | You may obtain a copy of the License at 61 | 62 | http://www.apache.org/licenses/LICENSE-2.0 63 | 64 | Unless required by applicable law or agreed to in writing, software 65 | distributed under the License is distributed on an "AS IS" BASIS, 66 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 67 | See the License for the specific language governing permissions and 68 | limitations under the License. 69 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | app.iml 3 | 4 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.aspsine.zhihu.daily" 9 | minSdkVersion 11 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | 25 | compile 'com.android.support:support-v4:22.1.0' 26 | compile 'com.android.support:appcompat-v7:22.1.0' 27 | compile 'com.android.support:recyclerview-v7:22.1.0' 28 | compile 'com.android.support:cardview-v7:22.1.0' 29 | compile 'com.google.code.gson:gson:2.3.1' 30 | compile 'com.jakewharton:butterknife:6.0.0' 31 | compile 'com.squareup.retrofit:retrofit:1.9.0' 32 | compile 'com.squareup.okhttp:okhttp:2.3.0' 33 | } 34 | -------------------------------------------------------------------------------- /app/libs/universal-image-loader-1.9.3-with-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/libs/universal-image-loader-1.9.3-with-sources.jar -------------------------------------------------------------------------------- /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 D:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/aspsine/zhihu/daily/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily; 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 | 11 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/App.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.os.StrictMode; 7 | 8 | import com.aspsine.zhihu.daily.respository.RepositoryImpl; 9 | import com.aspsine.zhihu.daily.respository.interfaces.Repository; 10 | import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; 11 | import com.nostra13.universalimageloader.core.ImageLoader; 12 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 13 | import com.nostra13.universalimageloader.core.assist.QueueProcessingType; 14 | 15 | /** 16 | * Created by sf on 2015/1/12. 17 | *

18 | * Application of the app 19 | */ 20 | public class App extends Application { 21 | private static Context applicationContext; 22 | private static Repository sRepository; 23 | 24 | @Override 25 | public void onCreate() { 26 | 27 | setStrictMode(); 28 | 29 | super.onCreate(); 30 | 31 | applicationContext = getApplicationContext(); 32 | 33 | // CrashHandler.getInstance(getApplicationContext()); 34 | 35 | initImageLoader(getApplicationContext()); 36 | } 37 | 38 | /** 39 | * issue: retrofit Memory leak in StrickMode 40 | * https://github.com/square/retrofit/issues/801 41 | */ 42 | private void setStrictMode() { 43 | if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 44 | StrictMode.enableDefaults(); 45 | } 46 | } 47 | 48 | private void initImageLoader(final Context context) { 49 | ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) 50 | .threadPriority(Thread.NORM_PRIORITY - 2) 51 | .denyCacheImageMultipleSizesInMemory() 52 | .diskCacheFileNameGenerator(new Md5FileNameGenerator()) 53 | .diskCacheSize(Constants.Config.IMAGE_CACHE_SIZE) // 50 Mb 54 | .tasksProcessingOrder(QueueProcessingType.LIFO) 55 | // .writeDebugLogs() // Remove for release app 56 | .build(); 57 | ImageLoader.getInstance().init(config); 58 | } 59 | 60 | public static Context getContext() { 61 | return applicationContext; 62 | } 63 | 64 | /** 65 | * NOTE:there is no multiThread use simple singleton 66 | * @return 67 | */ 68 | public static Repository getRepository() { 69 | if (sRepository == null) { 70 | sRepository = new RepositoryImpl(applicationContext); 71 | } 72 | return sRepository; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/Constants.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily; 2 | 3 | /** 4 | * Created by Aspsine on 2015/2/28. 5 | */ 6 | public class Constants { 7 | public static class Config { 8 | public static final String CHARSET = "UTF-8"; 9 | 10 | public static final String DATABASE_NAME = "DailyDB"; 11 | public static final int DATABASE_VERSION = 1; 12 | 13 | public static final int IMAGE_CACHE_SIZE = 50 * 1024 * 1024; 14 | 15 | public static final int HTTP_CACHE_SIZE = 20 * 1024 * 1024; 16 | public static final int HTTP_CONNECT_TIMEOUT = 15 * 1000; 17 | public static final int HTTP_READ_TIMEOUT = 20 * 1000; 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/CrashHandler.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.PackageManager.NameNotFoundException; 7 | import android.os.Build; 8 | import android.os.Debug; 9 | import android.os.Environment; 10 | import android.util.Log; 11 | 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.PrintWriter; 15 | import java.io.StringWriter; 16 | import java.io.Writer; 17 | import java.lang.Thread.UncaughtExceptionHandler; 18 | import java.lang.reflect.Field; 19 | import java.text.DateFormat; 20 | import java.text.SimpleDateFormat; 21 | import java.util.Date; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | /** 26 | * 处理程序中未捕获的异常,将异常写入日志文件 27 | */ 28 | public class CrashHandler implements UncaughtExceptionHandler { 29 | public static final String TAG = CrashHandler.class.getSimpleName(); 30 | 31 | private static CrashHandler instance = null; 32 | 33 | private Context mContext; 34 | 35 | private UncaughtExceptionHandler mDefaultHandler; 36 | 37 | private Map infos = new HashMap(); 38 | 39 | private CrashHandler() { 40 | } 41 | 42 | private CrashHandler(Context context) { 43 | mContext = context; 44 | mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 45 | Thread.setDefaultUncaughtExceptionHandler(this); 46 | } 47 | 48 | public static CrashHandler getInstance(Context context) { 49 | if (instance == null) { 50 | synchronized (CrashHandler.class) { 51 | if (instance == null) { 52 | instance = new CrashHandler(context); 53 | } 54 | } 55 | } 56 | return instance; 57 | } 58 | 59 | @Override 60 | public void uncaughtException(Thread thread, Throwable ex) { 61 | if (!handleException(ex) && mDefaultHandler != null) { 62 | mDefaultHandler.uncaughtException(thread, ex); 63 | } else { 64 | try { 65 | Thread.sleep(3000); 66 | } catch (InterruptedException e) { 67 | Log.e("error : ", e.getMessage()); 68 | } 69 | System.exit(0); 70 | } 71 | } 72 | 73 | private boolean handleException(final Throwable ex) { 74 | 75 | // 如果是调试状态则不生成异常文件,让系统默认的异常处理器来处理 76 | if (Debug.isDebuggerConnected()) 77 | return false; 78 | if (ex == null) 79 | return false; 80 | // 收集设备参数信息 81 | collectDeviceInfo(mContext); 82 | // 保存日志文件 83 | saveCrashInfo2File(ex); 84 | return true; 85 | } 86 | 87 | private void collectDeviceInfo(Context ctx) { 88 | try { 89 | PackageManager pm = ctx.getPackageManager(); 90 | PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); 91 | if (pi != null) { 92 | String versionName = pi.versionName == null ? "null" : pi.versionName; 93 | String versionCode = pi.versionCode + ""; 94 | infos.put("versionName", versionName); 95 | infos.put("versionCode", versionCode); 96 | } 97 | } catch (NameNotFoundException e) { 98 | Log.e(TAG, "an error occurred when collect package info", e); 99 | } 100 | Field[] fields = Build.class.getDeclaredFields(); 101 | for (Field field : fields) { 102 | try { 103 | field.setAccessible(true); 104 | infos.put(field.getName(), field.get(null).toString()); 105 | } catch (Exception e) { 106 | Log.e(TAG, "an error occurred when collect crash info", e); 107 | } 108 | } 109 | } 110 | 111 | private void saveCrashInfo2File(Throwable ex) { 112 | StringBuffer sb = new StringBuffer(); 113 | for (Map.Entry entry : infos.entrySet()) { 114 | String key = entry.getKey(); 115 | String value = entry.getValue(); 116 | sb.append(key + "=" + value + "\n"); 117 | } 118 | 119 | Writer writer = new StringWriter(); 120 | PrintWriter printWriter = new PrintWriter(writer); 121 | ex.printStackTrace(printWriter); 122 | Throwable cause = ex.getCause(); 123 | while (cause != null) { 124 | cause.printStackTrace(printWriter); 125 | cause = cause.getCause(); 126 | } 127 | printWriter.close(); 128 | String result = writer.toString(); 129 | sb.append(result); 130 | DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 131 | try { 132 | String fileName = String.format("crash-%s.log", df.format(new Date(System.currentTimeMillis()))); 133 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 134 | String path = "/sdcard/daily/log/"; 135 | File dir = new File(path); 136 | if (!dir.exists()) 137 | dir.mkdirs(); 138 | FileOutputStream fos = new FileOutputStream(path + fileName); 139 | fos.write(sb.toString().getBytes()); 140 | fos.close(); 141 | } 142 | } catch (Exception e) { 143 | Log.e(TAG, "an error occured while writing file...", e); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/animation/AnimationEndListener.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.animation; 2 | 3 | import android.view.animation.Animation; 4 | 5 | /** 6 | * Created by sf on 2015/1/13. 7 | */ 8 | public class AnimationEndListener implements Animation.AnimationListener { 9 | @Override 10 | public void onAnimationStart(Animation animation) { 11 | 12 | } 13 | 14 | @Override 15 | public void onAnimationEnd(Animation animation) { 16 | 17 | } 18 | 19 | @Override 20 | public void onAnimationRepeat(Animation animation) { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/api/DailyApi.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.api; 2 | 3 | /** 4 | * http://blog.robinchutaux.com/blog/using-retrofit-with-activeandroid/ 5 | * https://gist.github.com/polbins/1c7f9303d2b7d169a3b1 6 | * http://mattlogan.me/creating-a-retrofitlike-database-client 7 | * https://github.com/square/okhttp/wiki/Interceptors 8 | * https://github.com/square/retrofit/issues/693 9 | * Created by Aspsine on 2015/3/30. 10 | */ 11 | public class DailyApi { 12 | 13 | private static final String API = "http://news.at.zhihu.com/api/4"; 14 | 15 | private static DailyApiService dailyApiService; 16 | 17 | public static DailyApiService createApi() { 18 | if (dailyApiService == null) { 19 | synchronized (DailyApi.class) { 20 | if (dailyApiService == null) { 21 | dailyApiService = RestApi.createApi(DailyApiService.class, API); 22 | } 23 | } 24 | } 25 | return dailyApiService; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/api/DailyApiService.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.api; 2 | 3 | import com.aspsine.zhihu.daily.model.DailyStories; 4 | import com.aspsine.zhihu.daily.model.StartImage; 5 | import com.aspsine.zhihu.daily.model.Story; 6 | import com.aspsine.zhihu.daily.model.Theme; 7 | import com.aspsine.zhihu.daily.model.Themes; 8 | 9 | import retrofit.Callback; 10 | import retrofit.http.GET; 11 | import retrofit.http.Path; 12 | 13 | /** 14 | * Created by Aspsine on 2015/3/30. 15 | */ 16 | public interface DailyApiService { 17 | 18 | @GET("/start-image/{width}*{height}") 19 | void getStartImage(@Path("width") int width, @Path("height") int height, Callback callback); 20 | 21 | @GET("/news/latest") 22 | void getLatestDailyStories(Callback callback); 23 | 24 | @GET("/news/before/{date}") 25 | void getBeforeDailyStories(@Path("date") String date, Callback callback); 26 | 27 | @GET("/news/{storyId}") 28 | void getStoryDetail(@Path("storyId") String storyId, Callback callback); 29 | 30 | @GET("/themes") 31 | void getThemes(Callback callback); 32 | 33 | @GET("/theme/{themeId}") 34 | void getTheme(@Path("themeId") String themeId, Callback callback); 35 | 36 | @GET("/theme/{themeId}/before/{storyId}") 37 | void getThemeBeforeStory(@Path("themeId") String themeId, @Path("storyId") String storyId, Callback callback); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/api/RestApi.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.api; 2 | 3 | import com.aspsine.zhihu.daily.App; 4 | import com.aspsine.zhihu.daily.BuildConfig; 5 | import com.aspsine.zhihu.daily.network.OkHttp; 6 | import com.google.gson.GsonBuilder; 7 | 8 | import retrofit.RestAdapter; 9 | import retrofit.client.OkClient; 10 | import retrofit.converter.GsonConverter; 11 | 12 | /** 13 | * Created by Aspsine on 2015/4/1. 14 | */ 15 | public class RestApi { 16 | 17 | private static RestAdapter restAdapter; 18 | 19 | public static T createApi(Class clazz, String api) { 20 | if (restAdapter == null) { 21 | synchronized (RestApi.class) { 22 | if (restAdapter == null) { 23 | restAdapter = new RestAdapter.Builder() 24 | .setEndpoint(api) 25 | .setClient(new OkClient(OkHttp.createHttpClient(App.getContext()))) 26 | .setConverter(new GsonConverter(new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create())) 27 | .setLogLevel(BuildConfig.DEBUG ? RestAdapter.LogLevel.FULL : RestAdapter.LogLevel.NONE) 28 | .build(); 29 | } 30 | } 31 | } 32 | return restAdapter.create(clazz); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/db/CacheDao.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.db; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.database.sqlite.SQLiteDatabase; 6 | 7 | import com.aspsine.zhihu.daily.model.Cache; 8 | 9 | /** 10 | * Created by Aspsine on 2015/4/10. 11 | */ 12 | public class CacheDao { 13 | public static final String TABLE_NAME = Cache.class.getSimpleName(); 14 | private DBOpenHelper mHelper; 15 | 16 | public CacheDao(Context context) { 17 | mHelper = DBOpenHelper.getInstance(context); 18 | } 19 | 20 | public static void createTable(SQLiteDatabase db, boolean ifNotExists) { 21 | String constraint = ifNotExists ? "IF NOT EXISTS " : ""; 22 | db.execSQL("CREATE TABLE " + constraint 23 | + TABLE_NAME 24 | + " (" + 25 | "_id INTEGER PRIMARY KEY ," + // 0: id 26 | "request TEXT," + // 1: request 27 | "response TEXT," + // 2: response 28 | "time INTEGER);"); // 3: time 29 | } 30 | 31 | /** 32 | * Drops the underlying database table. 33 | */ 34 | public static void dropTable(SQLiteDatabase db, boolean ifExists) { 35 | String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + TABLE_NAME; 36 | db.execSQL(sql); 37 | } 38 | 39 | public void insertCache(Cache cache) { 40 | SQLiteDatabase db = mHelper.getWritableDatabase(); 41 | db.execSQL("insert into " 42 | + TABLE_NAME 43 | + " (request, response, time) values(?,?,?)", 44 | new Object[]{cache.getRequest(), cache.getResponse(), cache.getTime()}); 45 | db.close(); 46 | } 47 | 48 | public void deleteCache(String request) { 49 | SQLiteDatabase db = mHelper.getWritableDatabase(); 50 | db.execSQL("delete from " 51 | + TABLE_NAME 52 | + " where request=?", 53 | new Object[]{request}); 54 | db.close(); 55 | } 56 | 57 | public synchronized void updateCache(Cache cache) { 58 | SQLiteDatabase db = mHelper.getWritableDatabase(); 59 | Cursor cursor = db.rawQuery("select * from " 60 | + TABLE_NAME 61 | + " where request = ?", 62 | new String[]{cache.getRequest()}); 63 | 64 | boolean isExist = cursor.moveToNext(); 65 | if (isExist) { 66 | db.execSQL("delete from " 67 | + TABLE_NAME 68 | + " where request=?", 69 | new Object[]{cache.getRequest()}); 70 | } 71 | db.execSQL("insert into " 72 | + TABLE_NAME 73 | + " (request, response, time) values(?,?,?)", 74 | new Object[]{cache.getRequest(), cache.getResponse(), cache.getTime()}); 75 | cursor.close(); 76 | db.close(); 77 | } 78 | 79 | public synchronized Cache getCache(String request) { 80 | SQLiteDatabase db = mHelper.getReadableDatabase(); 81 | Cursor cursor = db.rawQuery("select * from " 82 | + TABLE_NAME 83 | + " where request = ?", 84 | new String[]{request}); 85 | 86 | Cache cache = new Cache(); 87 | while (cursor.moveToNext()) { 88 | cache.setRequest(cursor.getString(cursor.getColumnIndex("request"))); 89 | cache.setResponse(cursor.getString(cursor.getColumnIndex("response"))); 90 | cache.setTime(cursor.getLong(cursor.getColumnIndex("time"))); 91 | break; 92 | } 93 | cursor.close(); 94 | db.close(); 95 | return cache; 96 | } 97 | 98 | public boolean exists(String request) { 99 | SQLiteDatabase db = mHelper.getReadableDatabase(); 100 | Cursor cursor = db.rawQuery("select * from " 101 | + TABLE_NAME 102 | + " where request = ?", 103 | new String[]{request}); 104 | boolean isExist = cursor.moveToNext(); 105 | cursor.close(); 106 | db.close(); 107 | return isExist; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/db/DBOpenHelper.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.db; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | 7 | import com.aspsine.zhihu.daily.Constants; 8 | import com.aspsine.zhihu.daily.util.L; 9 | 10 | /** 11 | * Created by Aspsine on 2015/4/10. 12 | */ 13 | public class DBOpenHelper extends SQLiteOpenHelper { 14 | private static final String TAG = "Database"; 15 | 16 | private static final int DB_VERSION = Constants.Config.DATABASE_VERSION; 17 | private static final String DB_NAME = Constants.Config.DATABASE_NAME; 18 | 19 | private static DBOpenHelper sDBOpenHelper; 20 | /** 21 | * Creates underlying database table using DAOs. 22 | */ 23 | private void createAllTables(SQLiteDatabase db, boolean ifNotExists) { 24 | CacheDao.createTable(db, ifNotExists); 25 | } 26 | 27 | /** 28 | * Drops underlying database table using DAOs. 29 | */ 30 | private void dropAllTables(SQLiteDatabase db, boolean ifExists) { 31 | CacheDao.dropTable(db, ifExists); 32 | } 33 | 34 | public static DBOpenHelper getInstance(Context context) { 35 | if (sDBOpenHelper == null) { 36 | synchronized (DBOpenHelper.class) { 37 | if (sDBOpenHelper == null) { 38 | sDBOpenHelper = new DBOpenHelper(context); 39 | } 40 | } 41 | } 42 | return sDBOpenHelper; 43 | } 44 | 45 | private DBOpenHelper(Context context) { 46 | super(context, DB_NAME, null, DB_VERSION); 47 | } 48 | 49 | @Override 50 | public void onCreate(SQLiteDatabase db) { 51 | L.i(TAG, "Creating tables for DB version " + DB_VERSION); 52 | createAllTables(db, false); 53 | } 54 | 55 | @Override 56 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 57 | L.i(TAG, "Upgrading DB from version " + oldVersion + " to " + newVersion + " by dropping all tables"); 58 | dropAllTables(db, true); 59 | onCreate(db); 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/db/SQLUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.db; 2 | 3 | /** 4 | * Created by Aspsine on 2015/4/21. 5 | */ 6 | public class SQLUtils { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/interfaces/NavigationDrawerCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.interfaces; 2 | 3 | /** 4 | * Created by sf on 2015/1/16. 5 | * Callbacks interface that all activities using this fragment must implement. 6 | */ 7 | public interface NavigationDrawerCallbacks { 8 | /** 9 | * Called when an item in the navigation drawer is selected. 10 | */ 11 | void onNavigationDrawerItemSelected(int position); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/Cache.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | /** 6 | * Created by Aspsine on 2015/4/10. 7 | */ 8 | public class Cache { 9 | private long id; 10 | @Expose 11 | private String request; 12 | @Expose 13 | private String response; 14 | @Expose 15 | private long time; 16 | 17 | public Cache() { 18 | } 19 | 20 | public Cache(String request, String response, long time) { 21 | this.request = request; 22 | this.response = response; 23 | this.time = time; 24 | } 25 | 26 | public String getRequest() { 27 | return request; 28 | } 29 | 30 | public void setRequest(String request) { 31 | this.request = request; 32 | } 33 | 34 | public String getResponse() { 35 | return response; 36 | } 37 | 38 | public void setResponse(String response) { 39 | this.response = response; 40 | } 41 | 42 | public long getTime() { 43 | return time; 44 | } 45 | 46 | public void setTime(long time) { 47 | this.time = time; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/DailyStories.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Aspsine on 2015/3/10. 10 | */ 11 | public class DailyStories { 12 | @Expose 13 | private String date; 14 | @Expose 15 | private List stories; 16 | @Expose 17 | @SerializedName("top_stories") 18 | private List topStories; 19 | 20 | public String getDate() { 21 | return date; 22 | } 23 | 24 | public void setDate(String date) { 25 | this.date = date; 26 | } 27 | 28 | public List getStories() { 29 | return stories; 30 | } 31 | 32 | public void setStories(List stories) { 33 | this.stories = stories; 34 | } 35 | 36 | public List getTopStories() { 37 | return topStories; 38 | } 39 | 40 | public void setTopStories(List topStories) { 41 | this.topStories = topStories; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/Editor.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | /** 6 | * Created by aspsine on 15-3-25. 7 | */ 8 | public class Editor { 9 | @Expose 10 | private String id; 11 | @Expose 12 | private String avatar; 13 | @Expose 14 | private String name; 15 | 16 | public String getId() { 17 | return id; 18 | } 19 | 20 | public void setId(String id) { 21 | this.id = id; 22 | } 23 | 24 | public String getAvatar() { 25 | return avatar; 26 | } 27 | 28 | public void setAvatar(String avatar) { 29 | this.avatar = avatar; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/StartImage.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | /** 6 | * Created by Aspsine on 2015/3/30. 7 | */ 8 | public class StartImage { 9 | @Expose 10 | private String text; 11 | @Expose 12 | private String img; 13 | 14 | public String getText() { 15 | return text; 16 | } 17 | 18 | public void setText(String text) { 19 | this.text = text; 20 | } 21 | 22 | public String getImg() { 23 | return img; 24 | } 25 | 26 | public void setImg(String img) { 27 | this.img = img; 28 | } 29 | 30 | public boolean isValueEquals(StartImage startImage) { 31 | return this.text.equals(startImage.getText()) && this.img.equals(startImage.getImg()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/Story.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Aspsine on 2015/2/27. 10 | */ 11 | public class Story { 12 | @Expose 13 | private List images; 14 | @Expose 15 | private String type; 16 | @Expose 17 | private String id; 18 | @Expose 19 | @SerializedName("ga_prefix") 20 | private String gaPrefix; 21 | @Expose 22 | private String title; 23 | @Expose 24 | @SerializedName("multipic") 25 | private String multiPic; 26 | @Expose 27 | private String image; 28 | @Expose 29 | @SerializedName("share_url") 30 | private String shareUrl; 31 | @Expose 32 | private String body; 33 | @Expose 34 | @SerializedName("image_source") 35 | private String imageSource; 36 | @Expose 37 | @SerializedName("js") 38 | private List jsList; 39 | @Expose 40 | @SerializedName("css") 41 | private List cssList; 42 | @Expose 43 | private List recommenders; 44 | @Expose 45 | private Theme theme; 46 | 47 | private String thumbnail; 48 | private String url; 49 | private String sectionThumbnail; 50 | private String sectionId; 51 | private String sectionName; 52 | 53 | public List getImages() { 54 | return images; 55 | } 56 | 57 | public void setImages(List images) { 58 | this.images = images; 59 | } 60 | 61 | public String getType() { 62 | return type; 63 | } 64 | 65 | public void setType(String type) { 66 | this.type = type; 67 | } 68 | 69 | public String getId() { 70 | return id; 71 | } 72 | 73 | public void setId(String id) { 74 | this.id = id; 75 | } 76 | 77 | public String getGaPrefix() { 78 | return gaPrefix; 79 | } 80 | 81 | public void setGaPrefix(String gaPrefix) { 82 | this.gaPrefix = gaPrefix; 83 | } 84 | 85 | public String getTitle() { 86 | return title; 87 | } 88 | 89 | public void setTitle(String title) { 90 | this.title = title; 91 | } 92 | 93 | public String getMultiPic() { 94 | return multiPic; 95 | } 96 | 97 | public void setMultiPic(String multiPic) { 98 | this.multiPic = multiPic; 99 | } 100 | 101 | public String getImage() { 102 | return image; 103 | } 104 | 105 | public void setImage(String image) { 106 | this.image = image; 107 | } 108 | 109 | public String getThumbnail() { 110 | return thumbnail; 111 | } 112 | 113 | public void setThumbnail(String thumbnail) { 114 | this.thumbnail = thumbnail; 115 | } 116 | 117 | public String getShareUrl() { 118 | return shareUrl; 119 | } 120 | 121 | public void setShareUrl(String shareUrl) { 122 | this.shareUrl = shareUrl; 123 | } 124 | 125 | public String getUrl() { 126 | return url; 127 | } 128 | 129 | public void setUrl(String url) { 130 | this.url = url; 131 | } 132 | 133 | public String getBody() { 134 | return body; 135 | } 136 | 137 | public void setBody(String body) { 138 | this.body = body; 139 | } 140 | 141 | public String getImageSource() { 142 | return imageSource; 143 | } 144 | 145 | public void setImageSource(String imageSource) { 146 | this.imageSource = imageSource; 147 | } 148 | 149 | public String getSectionThumbnail() { 150 | return sectionThumbnail; 151 | } 152 | 153 | public void setSectionThumbnail(String sectionThumbnail) { 154 | this.sectionThumbnail = sectionThumbnail; 155 | } 156 | 157 | public String getSectionId() { 158 | return sectionId; 159 | } 160 | 161 | public void setSectionId(String sectionId) { 162 | this.sectionId = sectionId; 163 | } 164 | 165 | public List getJsList() { 166 | return jsList; 167 | } 168 | 169 | public void setJsList(List jsList) { 170 | this.jsList = jsList; 171 | } 172 | 173 | public String getSectionName() { 174 | return sectionName; 175 | } 176 | 177 | public void setSectionName(String sectionName) { 178 | this.sectionName = sectionName; 179 | } 180 | 181 | public List getCssList() { 182 | return cssList; 183 | } 184 | 185 | public void setCssList(List cssList) { 186 | this.cssList = cssList; 187 | } 188 | 189 | public List getRecommenders() { 190 | return recommenders; 191 | } 192 | 193 | public void setRecommenders(List recommenders) { 194 | this.recommenders = recommenders; 195 | } 196 | 197 | public Theme getTheme() { 198 | return theme; 199 | } 200 | 201 | public void setTheme(Theme theme) { 202 | this.theme = theme; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/Theme.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Aspsine on 2015/3/19. 9 | */ 10 | public class Theme { 11 | @Expose 12 | private String color; 13 | @Expose 14 | private String thumbnail; 15 | @Expose 16 | private String image; 17 | @Expose 18 | private String background; 19 | @Expose 20 | private String description; 21 | @Expose 22 | private String id; 23 | @Expose 24 | private String name; 25 | @Expose 26 | private List stories; 27 | @Expose 28 | private List editors; 29 | 30 | public String getColor() { 31 | return color; 32 | } 33 | 34 | public void setColor(String color) { 35 | this.color = color; 36 | } 37 | 38 | public String getThumbnail() { 39 | return thumbnail; 40 | } 41 | 42 | public void setThumbnail(String thumbnail) { 43 | this.thumbnail = thumbnail; 44 | } 45 | 46 | public String getImage() { 47 | return image; 48 | } 49 | 50 | public void setImage(String image) { 51 | this.image = image; 52 | } 53 | 54 | public String getBackground() { 55 | return background; 56 | } 57 | 58 | public void setBackground(String background) { 59 | this.background = background; 60 | } 61 | 62 | public String getDescription() { 63 | return description; 64 | } 65 | 66 | public void setDescription(String description) { 67 | this.description = description; 68 | } 69 | 70 | public String getId() { 71 | return id; 72 | } 73 | 74 | public void setId(String id) { 75 | this.id = id; 76 | } 77 | 78 | public String getName() { 79 | return name; 80 | } 81 | 82 | public void setName(String name) { 83 | this.name = name; 84 | } 85 | 86 | public List getStories() { 87 | return stories; 88 | } 89 | 90 | public void setStories(List stories) { 91 | this.stories = stories; 92 | } 93 | 94 | public List getEditors() { 95 | return editors; 96 | } 97 | 98 | public void setEditors(List editors) { 99 | this.editors = editors; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/model/Themes.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by Aspsine on 2015/3/19. 9 | */ 10 | public class Themes { 11 | @Expose 12 | private int limit; 13 | @Expose 14 | private List subscribed; 15 | @Expose 16 | private List others; 17 | 18 | public int getLimit() { 19 | return limit; 20 | } 21 | 22 | public void setLimit(int limit) { 23 | this.limit = limit; 24 | } 25 | 26 | public List getSubscribed() { 27 | return subscribed; 28 | } 29 | 30 | public void setSubscribed(List subscribed) { 31 | this.subscribed = subscribed; 32 | } 33 | 34 | public List getOthers() { 35 | return others; 36 | } 37 | 38 | public void setOthers(List others) { 39 | this.others = others; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/network/BaseHttp.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.network; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.HttpURLConnection; 7 | import java.net.URL; 8 | 9 | /** 10 | * Created by Aspsine on 2015/2/28. 11 | */ 12 | public class BaseHttp { 13 | 14 | protected static String get(String urlAddress) throws IOException { 15 | URL url = new URL(urlAddress); 16 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 17 | con.setRequestMethod("GET"); 18 | con.setRequestProperty("User-Agent", "Mozilla/5.0"); 19 | 20 | try { 21 | if (con.getResponseCode() == HttpURLConnection.HTTP_OK) { 22 | BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), Http.CHARSET)); 23 | String inputLine; 24 | StringBuilder response = new StringBuilder(); 25 | 26 | while ((inputLine = in.readLine()) != null) { 27 | response.append(inputLine); 28 | } 29 | 30 | in.close(); 31 | return response.toString(); 32 | } else { 33 | throw new IOException("Network Error - response code:" + con.getResponseCode()); 34 | } 35 | } finally { 36 | con.disconnect(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/network/BaseHttpClient.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.network; 2 | 3 | import org.apache.http.HttpResponse; 4 | import org.apache.http.HttpStatus; 5 | import org.apache.http.client.HttpClient; 6 | import org.apache.http.client.methods.HttpGet; 7 | import org.apache.http.impl.client.DefaultHttpClient; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | 14 | /** 15 | * Created by Aspsine on 2015/3/3. 16 | */ 17 | public class BaseHttpClient { 18 | 19 | protected static String get(String urlAddress) throws IOException { 20 | HttpGet httpGet = new HttpGet(urlAddress); 21 | HttpClient httpClient = new DefaultHttpClient(); 22 | HttpResponse httpResponse = httpClient.execute(httpGet); 23 | if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { 24 | InputStream inputStream = httpResponse.getEntity().getContent(); 25 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Http.CHARSET)); 26 | String inputLine; 27 | StringBuilder response = new StringBuilder(); 28 | 29 | while ((inputLine = reader.readLine()) != null) { 30 | response.append(inputLine); 31 | } 32 | inputStream.close(); 33 | reader.close(); 34 | return response.toString(); 35 | } 36 | throw new IOException(""); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/network/CacheInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.network; 2 | 3 | import com.aspsine.zhihu.daily.App; 4 | import com.aspsine.zhihu.daily.util.NetWorkUtils; 5 | import com.squareup.okhttp.Interceptor; 6 | import com.squareup.okhttp.Request; 7 | import com.squareup.okhttp.Response; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Created by Aspsine on 2015/4/1. 13 | */ 14 | public class CacheInterceptor implements Interceptor { 15 | private static final String CACHE_CONTROL = "Cache-Control"; 16 | 17 | @Override 18 | public Response intercept(Chain chain) throws IOException { 19 | 20 | Request request = chain.request(); 21 | // Add Cache Control only for GET methods 22 | if (request.method().equals("GET")) { 23 | if (NetWorkUtils.isNetWorkAvailable(App.getContext())) { 24 | request.newBuilder().addHeader(CACHE_CONTROL, "only-if-cached").build(); 25 | } else { 26 | request.newBuilder().addHeader(CACHE_CONTROL, "public, max-stale=2419200").build(); 27 | } 28 | } 29 | Response response = chain.proceed(request); 30 | // Re-write response CC header to force use of cache 31 | return response.newBuilder() 32 | .header("Cache-Control", "public, max-age=" + 60 * 10) // 10min 33 | .build(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/network/Http.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.network; 2 | 3 | import android.os.Build; 4 | 5 | import com.aspsine.zhihu.daily.Constants; 6 | import com.aspsine.zhihu.daily.util.L; 7 | 8 | import java.io.IOException; 9 | import java.io.UnsupportedEncodingException; 10 | import java.net.URLEncoder; 11 | import java.util.Iterator; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by Aspsine on 2015/2/28. 16 | */ 17 | public class Http { 18 | protected static final String CHARSET = Constants.Config.CHARSET; 19 | 20 | public static String get(String baseUrl) throws IOException { 21 | L.i("url", baseUrl); 22 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { 23 | return BaseHttpClient.get(baseUrl); 24 | } else { 25 | return BaseHttp.get(baseUrl); 26 | } 27 | } 28 | 29 | public static String get(String baseUrl, Map params) throws IOException { 30 | return get(makeUrl(baseUrl, params)); 31 | } 32 | 33 | public static String get(String baseUrl, String key, String value) throws IOException { 34 | return get(baseUrl + "?" + concatKeyValue(key, value)); 35 | } 36 | 37 | public static String get(String baseUrl, String suffix) throws IOException { 38 | return get(baseUrl + encodeString(suffix)); 39 | } 40 | 41 | public static String get(String baseUrl, String suffix, boolean replaceSpace) throws IOException { 42 | if (replaceSpace) { 43 | return get(baseUrl + encodeString(suffix).replace("+", "%20")); 44 | } else { 45 | return get(baseUrl, suffix); 46 | } 47 | } 48 | 49 | 50 | protected static String makeUrl(String baseUrl, Map params) { 51 | if (params == null || params.size() == 0) { 52 | return baseUrl; 53 | } 54 | 55 | StringBuilder sb = new StringBuilder(); 56 | sb.append(baseUrl); 57 | sb.append("?"); 58 | 59 | for (Iterator> iterator = params.entrySet().iterator(); iterator.hasNext(); sb.append("&")) { 60 | Map.Entry item = iterator.next(); 61 | sb.append(concatKeyValue(item.getKey(), item.getValue())); 62 | } 63 | 64 | return sb.toString(); 65 | } 66 | 67 | protected static String concatKeyValue(String key, String value) { 68 | return encodeString(key) + "=" + encodeString(value); 69 | } 70 | 71 | protected static String encodeString(String str) { 72 | try { 73 | return URLEncoder.encode(str, CHARSET); 74 | } catch (UnsupportedEncodingException e) { 75 | return ""; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/network/OkHttp.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.network; 2 | 3 | import android.content.Context; 4 | 5 | import com.aspsine.zhihu.daily.Constants; 6 | import com.aspsine.zhihu.daily.util.FileUtils; 7 | import com.squareup.okhttp.Cache; 8 | import com.squareup.okhttp.OkHttpClient; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * Created by Aspsine on 2015/3/31. 14 | */ 15 | public class OkHttp { 16 | private static OkHttpClient okHttpClient; 17 | 18 | public static OkHttpClient createHttpClient(Context context) { 19 | if (okHttpClient == null) { 20 | synchronized (OkHttp.class) { 21 | okHttpClient = new OkHttpClient(); 22 | okHttpClient.setCache(new Cache(FileUtils.getHttpCacheDir(context), Constants.Config.HTTP_CACHE_SIZE)); 23 | okHttpClient.setConnectTimeout(Constants.Config.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS); 24 | okHttpClient.setReadTimeout(Constants.Config.HTTP_READ_TIMEOUT, TimeUnit.MILLISECONDS); 25 | okHttpClient.networkInterceptors().add(new CacheInterceptor()); 26 | } 27 | } 28 | return okHttpClient; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/respository/CacheRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.respository; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | 6 | import com.aspsine.zhihu.daily.db.CacheDao; 7 | import com.aspsine.zhihu.daily.model.Cache; 8 | import com.aspsine.zhihu.daily.model.DailyStories; 9 | import com.aspsine.zhihu.daily.model.StartImage; 10 | import com.aspsine.zhihu.daily.model.Story; 11 | import com.aspsine.zhihu.daily.model.Theme; 12 | import com.aspsine.zhihu.daily.model.Themes; 13 | import com.aspsine.zhihu.daily.respository.interfaces.CacheRepository; 14 | import com.aspsine.zhihu.daily.util.L; 15 | import com.aspsine.zhihu.daily.util.SharedPrefUtils; 16 | import com.google.gson.Gson; 17 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 18 | import com.nostra13.universalimageloader.core.ImageLoader; 19 | import com.nostra13.universalimageloader.core.assist.ImageSize; 20 | 21 | import java.text.DateFormat; 22 | import java.text.SimpleDateFormat; 23 | import java.util.Calendar; 24 | 25 | /** 26 | * Created by Aspsine on 2015/4/10. 27 | */ 28 | public class CacheRepositoryImpl implements CacheRepository { 29 | private static final String TAG = CacheRepositoryImpl.class.getSimpleName(); 30 | private static DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 31 | private Context mContext; 32 | private CacheDao mCacheDao; 33 | private Gson mGson; 34 | 35 | public CacheRepositoryImpl(Context context) { 36 | this.mContext = context; 37 | this.mCacheDao = new CacheDao(context); 38 | this.mGson = new Gson(); 39 | } 40 | 41 | @Override 42 | public void getStartImage(Callback callback) { 43 | String mOldJsonString = SharedPrefUtils.getSplashJson(mContext); 44 | if (!TextUtils.isEmpty(mOldJsonString)) { 45 | StartImage startImage = new Gson().fromJson(mOldJsonString, StartImage.class); 46 | callback.success(startImage); 47 | } else { 48 | callback.failure(getException(StartImage.class)); 49 | } 50 | } 51 | 52 | @Override 53 | public void saveStartImage(int width, int height, DisplayImageOptions options, StartImage startImage) { 54 | String mOldJsonString = SharedPrefUtils.getSplashJson(mContext); 55 | StartImage old = new Gson().fromJson(mOldJsonString, StartImage.class); 56 | if (old == null || !startImage.getImg().equals(old.getImg())) { 57 | SharedPrefUtils.setSplashJson(mContext, new Gson().toJson(startImage)); 58 | ImageLoader.getInstance().loadImage(startImage.getImg(), new ImageSize(width, height), options, null); 59 | L.i(TAG, "new image."); 60 | } else { 61 | L.i(TAG, "old image."); 62 | } 63 | } 64 | 65 | @Override 66 | public void getLatestDailyStories(String url, Callback callback) { 67 | getDataObject(url, DailyStories.class, callback); 68 | } 69 | 70 | @Override 71 | public void saveLatestDailyStories(DailyStories dailyStories, String url) { 72 | saveCacheToDB(dailyStories, url); 73 | } 74 | 75 | @Override 76 | public void getBeforeDailyStories(String url, Callback callback) { 77 | getDataObject(url, DailyStories.class, callback); 78 | } 79 | 80 | @Override 81 | public void saveBeforeDailyStories(DailyStories dailyStories, String url) { 82 | saveCacheToDB(dailyStories, url); 83 | } 84 | 85 | @Override 86 | public void getStoryDetail(String url, Callback callback) { 87 | getDataObject(url, Story.class, callback); 88 | } 89 | 90 | @Override 91 | public void saveStoryDetail(Story story, String url) { 92 | saveCacheToDB(story, url); 93 | } 94 | 95 | @Override 96 | public void getThemes(String url, Callback callback) { 97 | getDataObject(url, Themes.class, callback); 98 | } 99 | 100 | @Override 101 | public void saveThemes(Themes themes, String url) { 102 | saveCacheToDB(themes, url); 103 | } 104 | 105 | @Override 106 | public void getTheme(String url, Callback callback) { 107 | getDataObject(url, Theme.class, callback); 108 | } 109 | 110 | @Override 111 | public void saveTheme(Theme theme, String url) { 112 | saveCacheToDB(theme, url); 113 | } 114 | 115 | @Override 116 | public void getThemeBeforeStory(String url, Callback callback) { 117 | getDataObject(url, Theme.class, callback); 118 | } 119 | 120 | @Override 121 | public void saveThemeBeforeStory(Theme theme, String url) { 122 | saveCacheToDB(theme, url); 123 | } 124 | 125 | private void getDataObject(String url, Class classOfT, CacheRepository.Callback callback) { 126 | String json = mCacheDao.getCache(url).getResponse(); 127 | T t = mGson.fromJson(json, classOfT); 128 | if (t != null) { 129 | callback.success(t); 130 | L.i(TAG, "get" + classOfT.getSimpleName() + " cache"); 131 | } else { 132 | callback.failure(getException(classOfT)); 133 | } 134 | } 135 | 136 | private void saveCacheToDB(Object o, String url) { 137 | Cache cache = new Cache(url, mGson.toJson(o), Long.valueOf(df.format(Calendar.getInstance().getTimeInMillis()))); 138 | mCacheDao.updateCache(cache); 139 | } 140 | 141 | private Exception getException(Class clazz) { 142 | return new Exception(clazz.getSimpleName() + " cache not found!"); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/respository/NetRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.respository; 2 | 3 | import com.aspsine.zhihu.daily.api.DailyApi; 4 | import com.aspsine.zhihu.daily.model.DailyStories; 5 | import com.aspsine.zhihu.daily.model.StartImage; 6 | import com.aspsine.zhihu.daily.model.Story; 7 | import com.aspsine.zhihu.daily.model.Theme; 8 | import com.aspsine.zhihu.daily.model.Themes; 9 | import com.aspsine.zhihu.daily.respository.interfaces.NetRepository; 10 | import com.aspsine.zhihu.daily.util.L; 11 | 12 | import retrofit.RetrofitError; 13 | import retrofit.client.Response; 14 | 15 | /** 16 | * Created by Aspsine on 2015/4/21. 17 | */ 18 | public class NetRepositoryImpl implements NetRepository { 19 | private static final String TAG = NetRepositoryImpl.class.getSimpleName(); 20 | 21 | @Override 22 | public void getStartImage(int width, int height, final Callback callback) { 23 | DailyApi.createApi().getStartImage(width, height, new retrofit.Callback() { 24 | @Override 25 | public void success(StartImage startImage, Response response) { 26 | callback.success(startImage, response.getUrl()); 27 | } 28 | 29 | @Override 30 | public void failure(RetrofitError error) { 31 | callback.failure(error, error.getUrl()); 32 | } 33 | }); 34 | } 35 | 36 | @Override 37 | public void getLatestDailyStories(final Callback callback) { 38 | DailyApi.createApi().getLatestDailyStories(new retrofit.Callback() { 39 | @Override 40 | public void success(DailyStories dailyStories, Response response) { 41 | callback.success(dailyStories, response.getUrl()); 42 | L.i(TAG, "getLatestDailyStories net"); 43 | } 44 | 45 | @Override 46 | public void failure(RetrofitError error) { 47 | callback.failure(error, error.getUrl()); 48 | } 49 | }); 50 | } 51 | 52 | @Override 53 | public void getBeforeDailyStories(String date, final Callback callback) { 54 | DailyApi.createApi().getBeforeDailyStories(date, new retrofit.Callback() { 55 | @Override 56 | public void success(DailyStories dailyStories, Response response) { 57 | callback.success(dailyStories, response.getUrl()); 58 | L.i(TAG, "getBeforeDailyStories net"); 59 | } 60 | 61 | @Override 62 | public void failure(RetrofitError error) { 63 | callback.failure(error, error.getUrl()); 64 | } 65 | }); 66 | } 67 | 68 | @Override 69 | public void getStoryDetail(String storyId, final Callback callback) { 70 | DailyApi.createApi().getStoryDetail(storyId, new retrofit.Callback() { 71 | @Override 72 | public void success(Story story, Response response) { 73 | callback.success(story, response.getUrl()); 74 | L.i(TAG, "getStoryDetail net"); 75 | } 76 | 77 | @Override 78 | public void failure(RetrofitError error) { 79 | callback.failure(error, error.getUrl()); 80 | } 81 | }); 82 | } 83 | 84 | @Override 85 | public void getThemes(final Callback callback) { 86 | DailyApi.createApi().getThemes(new retrofit.Callback() { 87 | @Override 88 | public void success(Themes themes, Response response) { 89 | callback.success(themes, response.getUrl()); 90 | L.i(TAG, "getThemes net"); 91 | } 92 | 93 | @Override 94 | public void failure(RetrofitError error) { 95 | callback.failure(error, error.getUrl()); 96 | } 97 | }); 98 | } 99 | 100 | @Override 101 | public void getTheme(String themeId, final Callback callback) { 102 | DailyApi.createApi().getTheme(themeId, new retrofit.Callback() { 103 | @Override 104 | public void success(Theme theme, Response response) { 105 | callback.success(theme, response.getUrl()); 106 | L.i(TAG, "getTheme net"); 107 | } 108 | 109 | @Override 110 | public void failure(RetrofitError error) { 111 | callback.failure(error, error.getUrl()); 112 | } 113 | }); 114 | } 115 | 116 | @Override 117 | public void getThemeBeforeStory(String themeId, String storyId, final Callback callback) { 118 | DailyApi.createApi().getThemeBeforeStory(themeId, storyId, new retrofit.Callback() { 119 | @Override 120 | public void success(Theme theme, Response response) { 121 | callback.success(theme, response.getUrl()); 122 | L.i(TAG, "getThemeBeforeStory net"); 123 | } 124 | 125 | @Override 126 | public void failure(RetrofitError error) { 127 | callback.failure(error, error.getUrl()); 128 | } 129 | }); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/respository/interfaces/CacheRepository.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.respository.interfaces; 2 | 3 | import com.aspsine.zhihu.daily.model.DailyStories; 4 | import com.aspsine.zhihu.daily.model.StartImage; 5 | import com.aspsine.zhihu.daily.model.Story; 6 | import com.aspsine.zhihu.daily.model.Theme; 7 | import com.aspsine.zhihu.daily.model.Themes; 8 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 9 | 10 | /** 11 | * Created by Aspsine on 2015/4/10. 12 | */ 13 | public interface CacheRepository { 14 | void getStartImage(Callback callback); 15 | 16 | void saveStartImage(int width, int height, DisplayImageOptions options, StartImage startImage); 17 | 18 | void getLatestDailyStories(String url, Callback callback); 19 | 20 | void saveLatestDailyStories(DailyStories dailyStories, String url); 21 | 22 | void getBeforeDailyStories(String url, Callback callback); 23 | 24 | void saveBeforeDailyStories(DailyStories dailyStories, String url); 25 | 26 | void getStoryDetail(String url, Callback callback); 27 | 28 | void saveStoryDetail(Story story, String url); 29 | 30 | void getThemes(String url, Callback callback); 31 | 32 | void saveThemes(Themes themes, String url); 33 | 34 | void getTheme(String url, Callback callback); 35 | 36 | void saveTheme(Theme theme, String url); 37 | 38 | void getThemeBeforeStory(String url, Callback callback); 39 | 40 | void saveThemeBeforeStory(Theme theme, String url); 41 | 42 | 43 | public interface Callback { 44 | 45 | /** 46 | * Successful 47 | * 48 | * @param t 49 | */ 50 | public void success(T t); 51 | 52 | 53 | /** 54 | * Unsuccessful 55 | * 56 | * @param e unexpected exception. 57 | */ 58 | public void failure(Exception e); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/respository/interfaces/NetRepository.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.respository.interfaces; 2 | 3 | import com.aspsine.zhihu.daily.model.DailyStories; 4 | import com.aspsine.zhihu.daily.model.StartImage; 5 | import com.aspsine.zhihu.daily.model.Story; 6 | import com.aspsine.zhihu.daily.model.Theme; 7 | import com.aspsine.zhihu.daily.model.Themes; 8 | 9 | /** 10 | * Created by Aspsine on 2015/4/21. 11 | */ 12 | public interface NetRepository { 13 | void getStartImage(int width, int height, Callback callback); 14 | 15 | void getLatestDailyStories(Callback callback); 16 | 17 | void getBeforeDailyStories(String date, Callback callback); 18 | 19 | void getStoryDetail(String storyId, Callback callback); 20 | 21 | void getThemes(Callback callback); 22 | 23 | void getTheme(String themeId, Callback callback); 24 | 25 | void getThemeBeforeStory(String themeId, String storyId, Callback callback); 26 | 27 | public interface Callback { 28 | 29 | /** 30 | * Successful 31 | * 32 | * @param t 33 | * @param url 34 | */ 35 | public void success(T t, String url); 36 | 37 | 38 | /** 39 | * Unsuccessful 40 | * 41 | * @param e unexpected exception. 42 | */ 43 | public void failure(Exception e, String url); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/respository/interfaces/Repository.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.respository.interfaces; 2 | 3 | import com.aspsine.zhihu.daily.model.DailyStories; 4 | import com.aspsine.zhihu.daily.model.StartImage; 5 | import com.aspsine.zhihu.daily.model.Story; 6 | import com.aspsine.zhihu.daily.model.Theme; 7 | import com.aspsine.zhihu.daily.model.Themes; 8 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 9 | 10 | 11 | /** 12 | * Created by Aspsine on 2015/4/21. 13 | */ 14 | public interface Repository { 15 | void getStartImage(int width, int height, DisplayImageOptions options, Callback callback); 16 | 17 | void getLatestDailyStories(Callback callback); 18 | 19 | void getBeforeDailyStories(String date, Callback callback); 20 | 21 | void getStoryDetail(String storyId, Callback callback); 22 | 23 | void getThemes(Callback callback); 24 | 25 | void getTheme(String themeId, Callback callback); 26 | 27 | void getThemeBeforeStory(String themeId, String storyId, Callback callback); 28 | 29 | public interface Callback { 30 | 31 | /** 32 | * Successful 33 | * 34 | * @param t 35 | * @param outDate 36 | */ 37 | public void success(T t, boolean outDate); 38 | 39 | 40 | /** 41 | * Unsuccessful 42 | * 43 | * @param e unexpected exception. 44 | */ 45 | public void failure(Exception e); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/activity/BaseAppCompatActivity.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.v7.app.ActionBar; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.MenuItem; 10 | 11 | import com.aspsine.zhihu.daily.R; 12 | import com.aspsine.zhihu.daily.util.UIUtils; 13 | 14 | /** 15 | * Created by Aspsine on 2015/3/24. 16 | */ 17 | public abstract class BaseAppCompatActivity extends AppCompatActivity { 18 | public Toolbar mActionBarToolbar; 19 | 20 | protected abstract int getContentViewLayoutId(); 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(getContentViewLayoutId()); 26 | mActionBarToolbar = (Toolbar) findViewById(R.id.actionbarToolbar); 27 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { 28 | int statusBarHeight = UIUtils.getStatusBarHeight(this); 29 | mActionBarToolbar.setPadding(0, statusBarHeight, 0, 0); 30 | } 31 | setupActionBar(); 32 | } 33 | 34 | private void setupActionBar() { 35 | setSupportActionBar(mActionBarToolbar); 36 | ActionBar actionBar = getSupportActionBar(); 37 | if (actionBar != null && !(this instanceof NavigationDrawerActivity)) { 38 | actionBar.setDisplayHomeAsUpEnabled(true); 39 | } 40 | } 41 | 42 | @Override 43 | public boolean onOptionsItemSelected(MenuItem item) { 44 | // Handle action bar item clicks here. The action bar will 45 | // automatically handle clicks on the Home/Up button, so long 46 | // as you specify a parent activity in AndroidManifest.xml. 47 | int id = item.getItemId(); 48 | 49 | if (id == android.R.id.home) { 50 | onBackPressed(); 51 | return true; 52 | } else if (id == R.id.action_settings) { 53 | // startActivity(new Intent(this, SettingsActivity.class)); 54 | return true; 55 | } 56 | 57 | return super.onOptionsItemSelected(item); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/activity/GuiderActivity.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.FragmentActivity; 5 | import android.support.v4.app.FragmentManager; 6 | 7 | import com.aspsine.zhihu.daily.R; 8 | import com.aspsine.zhihu.daily.ui.fragment.GuideFragment; 9 | import com.aspsine.zhihu.daily.ui.fragment.SplashFragment; 10 | import com.aspsine.zhihu.daily.util.IntentUtils; 11 | import com.aspsine.zhihu.daily.util.SharedPrefUtils; 12 | 13 | public class GuiderActivity extends FragmentActivity { 14 | private SplashFragment mSplashFragment; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_guider); 20 | 21 | FragmentManager fm = getSupportFragmentManager(); 22 | 23 | if (SharedPrefUtils.isFirstLaunch(this)) { 24 | guide(fm); 25 | } else { 26 | splash(fm); 27 | } 28 | } 29 | 30 | void splash(FragmentManager fm) { 31 | 32 | mSplashFragment = new SplashFragment(); 33 | 34 | if (fm.findFragmentByTag(SplashFragment.TAG) == null) { 35 | fm.beginTransaction() 36 | .add(R.id.container, mSplashFragment, SplashFragment.TAG) 37 | .commit(); 38 | } 39 | } 40 | 41 | void guide(FragmentManager fm) { 42 | if (fm.findFragmentByTag(GuideFragment.TAG) == null) { 43 | fm.beginTransaction() 44 | .add(R.id.container, new GuideFragment(), GuideFragment.TAG) 45 | .commit(); 46 | } 47 | } 48 | 49 | public void intentToMainActivity() { 50 | IntentUtils.intentToMainActivity(this); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/activity/NavigationDrawerActivity.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentTransaction; 7 | import android.support.v4.widget.DrawerLayout; 8 | import android.support.v7.app.ActionBar; 9 | import android.view.Menu; 10 | 11 | import com.aspsine.zhihu.daily.R; 12 | import com.aspsine.zhihu.daily.interfaces.NavigationDrawerCallbacks; 13 | import com.aspsine.zhihu.daily.ui.fragment.BaseFragment; 14 | import com.aspsine.zhihu.daily.ui.fragment.DailyStoriesFragment; 15 | import com.aspsine.zhihu.daily.ui.fragment.NavigationFragment; 16 | import com.aspsine.zhihu.daily.ui.fragment.ThemeStoriesFragment; 17 | 18 | public class NavigationDrawerActivity extends BaseAppCompatActivity implements NavigationDrawerCallbacks { 19 | private static final String TAG = NavigationDrawerActivity.class.getSimpleName(); 20 | 21 | private NavigationFragment mNavigationFragment; 22 | 23 | private CharSequence mTitle = ""; 24 | 25 | @Override 26 | protected int getContentViewLayoutId() { 27 | return R.layout.activity_navigation_drawer; 28 | } 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setUpDrawer(); 34 | if (savedInstanceState == null) { 35 | mNavigationFragment.selectItem(NavigationFragment.getDefaultNavDrawerItem()); 36 | } 37 | } 38 | 39 | private void setUpDrawer() { 40 | mNavigationFragment = (NavigationFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); 41 | mTitle = getTitle(); 42 | mNavigationFragment.setup(R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout), mActionBarToolbar); 43 | } 44 | 45 | private int lastPosition = 0; 46 | 47 | @Override 48 | public void onNavigationDrawerItemSelected(int position) { 49 | // update the main content by replacing fragments 50 | FragmentManager fm = getSupportFragmentManager(); 51 | FragmentTransaction ft = fm.beginTransaction(); 52 | 53 | Fragment lastFragment = fm.findFragmentByTag(getTag(lastPosition)); 54 | if (lastFragment != null) { 55 | ft.detach(lastFragment); 56 | } 57 | 58 | Fragment fragment = fm.findFragmentByTag(getTag(position)); 59 | if (fragment == null) { 60 | fragment = getFragmentItem(position); 61 | ft.add(R.id.container, fragment, getTag(position)); 62 | } else { 63 | ft.attach(fragment); 64 | } 65 | 66 | ft.commit(); 67 | lastPosition = position; 68 | 69 | mTitle = mNavigationFragment.getTitle(position); 70 | restoreActionBar(); 71 | } 72 | 73 | private int getId(Fragment fragment) { 74 | return ((BaseFragment) fragment).getThemeNumber(); 75 | } 76 | 77 | private String getTag(int position) { 78 | switch (position) { 79 | case 0: 80 | return DailyStoriesFragment.TAG; 81 | default: 82 | return ThemeStoriesFragment.TAG + position; 83 | } 84 | } 85 | 86 | private Fragment getFragmentItem(int position) { 87 | return BaseFragment.newInstance(position, mNavigationFragment.getSectionId(position)); 88 | } 89 | 90 | public void restoreActionBar() { 91 | ActionBar actionBar = getSupportActionBar(); 92 | actionBar.setDisplayShowTitleEnabled(true); 93 | actionBar.setTitle(mTitle); 94 | } 95 | 96 | @Override 97 | public boolean onCreateOptionsMenu(Menu menu) { 98 | if (!mNavigationFragment.isDrawerOpen()) { 99 | // Only show items in the action bar relevant to this screen 100 | // if the drawer is not showing. Otherwise, let the drawer 101 | // decide what to show in the action bar. 102 | getMenuInflater().inflate(R.menu.menu_navigation_drawer, menu); 103 | return true; 104 | } 105 | return super.onCreateOptionsMenu(menu); 106 | } 107 | 108 | @Override 109 | public void onBackPressed() { 110 | if (mNavigationFragment.isDrawerOpen()) { 111 | mNavigationFragment.closeDrawer(); 112 | } else { 113 | if (mNavigationFragment.getCurrentSelectedPosition() != NavigationFragment.getDefaultNavDrawerItem()) { 114 | mNavigationFragment.selectItem(NavigationFragment.getDefaultNavDrawerItem()); 115 | } else { 116 | super.onBackPressed(); 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.activity; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.aspsine.zhihu.daily.R; 6 | import com.aspsine.zhihu.daily.ui.fragment.SettingsFragment; 7 | 8 | public class SettingsActivity extends BaseAppCompatActivity { 9 | 10 | @Override 11 | protected int getContentViewLayoutId() { 12 | return R.layout.activity_settings; 13 | } 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | if (savedInstanceState == null) { 19 | getFragmentManager().beginTransaction() 20 | .replace(R.id.container, new SettingsFragment()) 21 | .commit(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/activity/StoryActivity.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | 7 | import com.aspsine.zhihu.daily.R; 8 | import com.aspsine.zhihu.daily.ui.fragment.DailyStoriesFragment; 9 | import com.aspsine.zhihu.daily.ui.fragment.StoryFragment; 10 | import com.aspsine.zhihu.daily.util.L; 11 | 12 | public class StoryActivity extends BaseAppCompatActivity { 13 | 14 | @Override 15 | protected int getContentViewLayoutId() { 16 | return R.layout.activity_story; 17 | } 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | getSupportActionBar().setTitle(null); 23 | if (savedInstanceState == null) { 24 | String storyId = getIntent().getStringExtra(DailyStoriesFragment.EXTRA_STORY_ID); 25 | StoryFragment storyFragment = StoryFragment.newInstance(storyId); 26 | storyFragment.setToolBar(mActionBarToolbar); 27 | getSupportFragmentManager().beginTransaction() 28 | .add(R.id.container, storyFragment, StoryFragment.TAG) 29 | .commit(); 30 | } 31 | } 32 | 33 | @Override 34 | public void onBackPressed() { 35 | super.onBackPressed(); 36 | finish(); 37 | } 38 | 39 | @Override 40 | protected void onDestroy() { 41 | L.i("StoryActivity", "onDestroy"); 42 | super.onDestroy(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/adapter/ThemeStoriesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.text.TextUtils; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.aspsine.zhihu.daily.R; 9 | import com.aspsine.zhihu.daily.model.Editor; 10 | import com.aspsine.zhihu.daily.model.Story; 11 | import com.aspsine.zhihu.daily.model.Theme; 12 | import com.aspsine.zhihu.daily.ui.adapter.holder.StoryViewHolder; 13 | import com.aspsine.zhihu.daily.ui.widget.AvatarsView; 14 | import com.aspsine.zhihu.daily.ui.widget.StoryHeaderView; 15 | import com.aspsine.zhihu.daily.util.DensityUtil; 16 | import com.aspsine.zhihu.daily.util.UIUtils; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by Aspsine on 2015/3/26. 23 | */ 24 | public class ThemeStoriesAdapter extends RecyclerView.Adapter { 25 | private Theme mTheme; 26 | 27 | public ThemeStoriesAdapter() { 28 | } 29 | 30 | public void setTheme(Theme theme) { 31 | mTheme = theme; 32 | notifyDataSetChanged(); 33 | } 34 | 35 | public void appendStories(List stories) { 36 | mTheme.getStories().addAll(stories); 37 | notifyDataSetChanged(); 38 | } 39 | 40 | public static final class Type { 41 | public static final int TYPE_HEADER = 0; 42 | public static final int TYPE_AVATARS = 1; 43 | public static final int TYPE_ITEM = 2; 44 | } 45 | 46 | @Override 47 | public int getItemViewType(int position) { 48 | if (!TextUtils.isEmpty(mTheme.getBackground()) && position == 0) { 49 | return Type.TYPE_HEADER; 50 | } else if (hasEditors() && position == 1) { 51 | return Type.TYPE_AVATARS; 52 | } else { 53 | return Type.TYPE_ITEM; 54 | } 55 | } 56 | 57 | @Override 58 | public int getItemCount() { 59 | int count = 0; 60 | if (mTheme != null) { 61 | if (!TextUtils.isEmpty(mTheme.getBackground())) { 62 | count += 1; 63 | } 64 | if (mTheme.getEditors() != null && mTheme.getEditors().size() > 0) { 65 | count += 1; 66 | } 67 | if (mTheme.getStories() != null) { 68 | count = count + mTheme.getStories().size(); 69 | } 70 | } 71 | return count; 72 | } 73 | 74 | @Override 75 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 76 | RecyclerView.ViewHolder viewHolder = null; 77 | switch (viewType) { 78 | case Type.TYPE_HEADER: 79 | viewHolder = new HeaderViewHolder(StoryHeaderView.newInstance(parent)); 80 | break; 81 | case Type.TYPE_AVATARS: 82 | AvatarsView avatarsView = new AvatarsView(parent.getContext()); 83 | RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 84 | lp.setMargins(0, DensityUtil.dip2px(parent.getContext(), 8), 0, 0); 85 | avatarsView.setLayoutParams(lp); 86 | viewHolder = new AvatarViewHolder(avatarsView); 87 | break; 88 | case Type.TYPE_ITEM: 89 | viewHolder = new StoryViewHolder(UIUtils.inflate(R.layout.recycler_item_story, parent)); 90 | break; 91 | } 92 | return viewHolder; 93 | } 94 | 95 | @Override 96 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 97 | int viewType = getItemViewType(position); 98 | switch (viewType) { 99 | case Type.TYPE_HEADER: 100 | ((StoryHeaderView) holder.itemView) 101 | .bindData(mTheme.getDescription(), null, mTheme.getBackground()); 102 | break; 103 | case Type.TYPE_AVATARS: 104 | List images = new ArrayList<>(); 105 | for (Editor editor : mTheme.getEditors()) { 106 | images.add(editor.getAvatar()); 107 | } 108 | ((AvatarsView) holder.itemView).bindData(holder.itemView.getResources().getString(R.string.avatar_title_editor), images); 109 | break; 110 | case Type.TYPE_ITEM: 111 | StoryViewHolder storyViewHolder = (StoryViewHolder) holder; 112 | final int storyPosition = hasEditors() ? position - 2 : position - 1; 113 | storyViewHolder.bindStoryView(mTheme.getStories().get(storyPosition)); 114 | break; 115 | default: 116 | throw new IllegalArgumentException("error view type!"); 117 | } 118 | } 119 | 120 | private boolean hasEditors() { 121 | return mTheme.getEditors() != null && mTheme.getEditors().size() > 0; 122 | } 123 | 124 | public static final class HeaderViewHolder extends RecyclerView.ViewHolder { 125 | public HeaderViewHolder(View itemView) { 126 | super(itemView); 127 | } 128 | } 129 | 130 | public static final class AvatarViewHolder extends RecyclerView.ViewHolder { 131 | 132 | public AvatarViewHolder(View itemView) { 133 | super(itemView); 134 | } 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/adapter/holder/DateViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.adapter.holder; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.aspsine.zhihu.daily.R; 9 | import com.aspsine.zhihu.daily.util.DateUtils; 10 | 11 | /** 12 | * Created by Aspsine on 2015/3/11. 13 | */ 14 | public class DateViewHolder extends RecyclerView.ViewHolder { 15 | public TextView tvDate; 16 | 17 | public DateViewHolder(View itemView) { 18 | super(itemView); 19 | 20 | tvDate = (TextView) itemView.findViewById(R.id.date); 21 | } 22 | 23 | public void bindDateView(String date) { 24 | tvDate.setText(getDate(date, itemView.getContext())); 25 | } 26 | 27 | public static String getDate(String date, Context context) { 28 | if (DateUtils.isToday(date)) { 29 | return context.getResources().getString(R.string.recycler_item_main_today_hottest); 30 | } else { 31 | return DateUtils.getMainPageDate(date); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/adapter/holder/HeaderViewPagerHolder.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.adapter.holder; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.aspsine.zhihu.daily.R; 11 | import com.aspsine.zhihu.daily.model.Story; 12 | import com.aspsine.zhihu.daily.ui.widget.CirclePageIndicator; 13 | import com.aspsine.zhihu.daily.ui.widget.MyViewPager; 14 | import com.aspsine.zhihu.daily.ui.widget.StoryHeaderView; 15 | import com.aspsine.zhihu.daily.util.IntentUtils; 16 | import com.aspsine.zhihu.daily.util.ListUtils; 17 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * Created by Aspsine on 2015/3/11. 23 | */ 24 | public class HeaderViewPagerHolder extends RecyclerView.ViewHolder { 25 | private static final String TAG = HeaderViewPagerHolder.class.getSimpleName(); 26 | private MyViewPager viewPager; 27 | private CirclePageIndicator indicator; 28 | private PagerAdapter mPagerAdapter; 29 | 30 | public HeaderViewPagerHolder(@Nullable View itemView, List stories) { 31 | super(itemView); 32 | 33 | viewPager = (MyViewPager) itemView.findViewById(R.id.viewPager); 34 | indicator = (CirclePageIndicator) itemView.findViewById(R.id.indicator); 35 | if (ListUtils.isEmpty(stories)) { 36 | return; 37 | } else if (stories.size() < 2) { 38 | indicator.setVisibility(View.GONE); 39 | } 40 | mPagerAdapter = new HeaderPagerAdapter(stories); 41 | } 42 | 43 | public void bindHeaderView() { 44 | if (viewPager.getAdapter() == null) { 45 | viewPager.setAdapter(mPagerAdapter); 46 | indicator.setViewPager(viewPager); 47 | } else { 48 | mPagerAdapter.notifyDataSetChanged(); 49 | } 50 | } 51 | 52 | public boolean isAutoScrolling() { 53 | if (viewPager != null) { 54 | return viewPager.isAutoScrolling(); 55 | } 56 | return false; 57 | } 58 | 59 | public void stopAutoScroll() { 60 | if (viewPager != null) { 61 | viewPager.stopAutoScroll(); 62 | } 63 | } 64 | 65 | public void startAutoScroll() { 66 | if (viewPager != null) { 67 | viewPager.startAutoScroll(); 68 | } 69 | } 70 | 71 | private final static class HeaderPagerAdapter extends PagerAdapter { 72 | private List mStories; 73 | private DisplayImageOptions mOptions; 74 | 75 | private int mChildCount; 76 | 77 | public HeaderPagerAdapter(List stories) { 78 | mStories = stories; 79 | this.mOptions = new DisplayImageOptions.Builder() 80 | .cacheInMemory(true) 81 | .cacheOnDisk(true) 82 | .considerExifParams(true) 83 | .build(); 84 | } 85 | 86 | @Override 87 | public int getCount() { 88 | return mStories == null ? 0 : mStories.size(); 89 | } 90 | 91 | @Override 92 | public boolean isViewFromObject(View view, Object object) { 93 | return view == object; 94 | } 95 | 96 | @Override 97 | public Object instantiateItem(final ViewGroup container, final int position) { 98 | StoryHeaderView storyHeaderView = StoryHeaderView.newInstance(container); 99 | final Story story = mStories.get(position); 100 | storyHeaderView.bindData(story.getTitle(), story.getImageSource(), story.getImage(), mOptions); 101 | storyHeaderView.setOnClickListener(new View.OnClickListener() { 102 | @Override 103 | public void onClick(View v) { 104 | IntentUtils.intentToStoryActivity((Activity) v.getContext(), story); 105 | } 106 | }); 107 | container.addView(storyHeaderView); 108 | return storyHeaderView; 109 | } 110 | 111 | @Override 112 | public void destroyItem(ViewGroup container, int position, Object object) { 113 | container.removeView((StoryHeaderView) object); 114 | } 115 | 116 | @Override 117 | public void notifyDataSetChanged() { 118 | super.notifyDataSetChanged(); 119 | mChildCount = getCount(); 120 | } 121 | 122 | @Override 123 | public int getItemPosition(Object object) { 124 | if (mChildCount > 0) { 125 | mChildCount--; 126 | return POSITION_NONE; 127 | } 128 | return super.getItemPosition(object); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/adapter/holder/RecyclerHeaderViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.adapter.holder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by Aspsine on 2015/3/2. 8 | */ 9 | public class RecyclerHeaderViewHolder extends RecyclerView.ViewHolder { 10 | 11 | public RecyclerHeaderViewHolder(View itemView) { 12 | super(itemView); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/adapter/holder/StoryViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.adapter.holder; 2 | 3 | import android.app.Activity; 4 | import android.support.v7.widget.CardView; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.text.Html; 7 | import android.text.TextUtils; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | import android.widget.RelativeLayout; 11 | import android.widget.TextView; 12 | 13 | import com.aspsine.zhihu.daily.R; 14 | import com.aspsine.zhihu.daily.model.Story; 15 | import com.aspsine.zhihu.daily.ui.activity.NavigationDrawerActivity; 16 | import com.aspsine.zhihu.daily.ui.fragment.NavigationFragment; 17 | import com.aspsine.zhihu.daily.util.IntentUtils; 18 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 19 | import com.nostra13.universalimageloader.core.ImageLoader; 20 | 21 | /** 22 | * Created by Aspsine on 2015/3/11. 23 | */ 24 | public class StoryViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 25 | private CardView card; 26 | private TextView text; 27 | private TextView theme; 28 | private ImageView image; 29 | private ImageView ivMultiPic; 30 | private ImageView line; 31 | private RelativeLayout rlContent; 32 | private DisplayImageOptions mOptions; 33 | private Story mStory; 34 | 35 | public StoryViewHolder(View itemView) { 36 | super(itemView); 37 | 38 | this.mOptions = new DisplayImageOptions.Builder() 39 | .showImageOnLoading(R.drawable.image_small_default) 40 | .showImageForEmptyUri(R.drawable.image_small_default) 41 | .showImageOnFail(R.drawable.image_small_default) 42 | .cacheInMemory(true) 43 | .cacheOnDisk(true) 44 | .considerExifParams(true) 45 | .build(); 46 | 47 | card = (CardView) itemView.findViewById(R.id.card); 48 | text = (TextView) itemView.findViewById(R.id.text); 49 | image = (ImageView) itemView.findViewById(R.id.image); 50 | ivMultiPic = (ImageView) itemView.findViewById(R.id.ivMultiPic); 51 | rlContent = (RelativeLayout) itemView.findViewById(R.id.rl_content); 52 | theme = (TextView) itemView.findViewById(R.id.theme); 53 | line = (ImageView) itemView.findViewById(R.id.line); 54 | card.setOnClickListener(this); 55 | theme.setOnClickListener(this); 56 | } 57 | 58 | @Override 59 | public void onClick(View v) { 60 | if (v.getId() == R.id.card) { 61 | IntentUtils.intentToStoryActivity((Activity) v.getContext(), mStory); 62 | } else if (v.getId() == R.id.theme) { 63 | final NavigationFragment navigationFragment = (NavigationFragment) ((NavigationDrawerActivity) itemView.getContext()).getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); 64 | if (navigationFragment != null && navigationFragment.isAdded()) { 65 | navigationFragment.openDrawer(); 66 | v.postDelayed(new Runnable() { 67 | @Override 68 | public void run() { 69 | navigationFragment.selectTheme(mStory.getTheme()); 70 | } 71 | }, 500); 72 | } 73 | } 74 | } 75 | 76 | public void bindStoryView(Story story) { 77 | mStory = story; 78 | if (mStory.getTheme() == null) { 79 | theme.setVisibility(View.GONE); 80 | line.setVisibility(View.GONE); 81 | } else { 82 | theme.setVisibility(View.VISIBLE); 83 | line.setVisibility(View.VISIBLE); 84 | StringBuilder sb = new StringBuilder(); 85 | sb.append(itemView.getResources().getString(R.string.recycler_item_main_select_from)) 86 | .append(" ").append("") 87 | .append(mStory.getTheme().getName()) 88 | .append(""); 89 | theme.setText(Html.fromHtml(sb.toString())); 90 | } 91 | text.setText(String.valueOf(mStory.getTitle())); 92 | 93 | String imageUrl = mStory.getImages() == null ? "" : mStory.getImages().get(0); 94 | if (TextUtils.isEmpty(imageUrl) || TextUtils.isEmpty(mStory.getMultiPic())) { 95 | ivMultiPic.setVisibility(View.GONE); 96 | } else if (Boolean.valueOf(mStory.getMultiPic())) { 97 | ivMultiPic.setVisibility(View.VISIBLE); 98 | } 99 | 100 | if (TextUtils.isEmpty(imageUrl)) { 101 | image.setVisibility(View.GONE); 102 | } else { 103 | image.setVisibility(View.VISIBLE); 104 | ImageLoader.getInstance().displayImage(imageUrl, image, mOptions); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | 6 | /** 7 | * Created by Aspsine on 2015/3/19. 8 | */ 9 | public class BaseFragment extends Fragment { 10 | private static final String TAG = BaseFragment.class.getSimpleName(); 11 | /** 12 | * The fragment argument representing the section number for this 13 | * fragment. 14 | */ 15 | public static final String ARG_THEME_NUMBER = "theme_number"; 16 | public static final String ARG_THEME_ID = "theme_id"; 17 | 18 | public static final int THEME_NUMBER_MAIN = 0; 19 | public static final String THEME_ID_MAIN = "section_id_main"; 20 | 21 | public static final String EXTRA_STORY_ID = "extra_story_id"; 22 | 23 | private int mArgThemeNumber; 24 | 25 | private String mArgThemeId; 26 | 27 | public BaseFragment() { 28 | } 29 | 30 | public static BaseFragment newInstance(int position, String sectionId) { 31 | Bundle bundle = new Bundle(); 32 | bundle.putInt(ARG_THEME_NUMBER, position); 33 | bundle.putString(ARG_THEME_ID, sectionId); 34 | BaseFragment fragment = null; 35 | if (position == 0) { 36 | fragment = new DailyStoriesFragment(); 37 | } else { 38 | fragment = new ThemeStoriesFragment(); 39 | } 40 | fragment.setArguments(bundle); 41 | return fragment; 42 | } 43 | 44 | @Override 45 | public void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | mArgThemeNumber = getArguments().getInt(ARG_THEME_NUMBER); 48 | mArgThemeId = getArguments().getString(ARG_THEME_ID); 49 | } 50 | 51 | public int getThemeNumber() { 52 | return mArgThemeNumber; 53 | } 54 | 55 | public String getThemeId() { 56 | return mArgThemeId; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/fragment/GuideFragment.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.fragment; 2 | 3 | import android.app.Activity; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | import android.text.Html; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.TextView; 12 | 13 | import com.aspsine.zhihu.daily.R; 14 | import com.aspsine.zhihu.daily.ui.activity.GuiderActivity; 15 | import com.aspsine.zhihu.daily.util.SharedPrefUtils; 16 | 17 | /** 18 | * 19 | */ 20 | public class GuideFragment extends Fragment { 21 | public static final String TAG = GuideFragment.class.getSimpleName(); 22 | 23 | String hello = "

Welcome

This application is a simple demonstration of Zhihu Daily Android developed by me. It's a free app. All the information, content and api copyright is belong to Zhihu. Inc.

-Aspsine

About

Email: littleximail@gmail.com

Github: github.com/aspsine

"; 24 | 25 | @Override 26 | public void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | } 29 | 30 | @Override 31 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 32 | View rootView = inflater.inflate(R.layout.fragment_guide, container, false); 33 | TextView tvHello = (TextView) rootView.findViewById(R.id.tvHello); 34 | tvHello.setText(Html.fromHtml(hello)); 35 | rootView.findViewById(R.id.btnEnter).setOnClickListener(new View.OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | SharedPrefUtils.markFirstLaunch(getActivity()); 39 | ((GuiderActivity)getActivity()).intentToMainActivity(); 40 | } 41 | }); 42 | return rootView; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/fragment/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.fragment; 2 | 3 | 4 | import android.app.Fragment; 5 | import android.os.Bundle; 6 | import android.preference.PreferenceFragment; 7 | import android.view.LayoutInflater; 8 | import android.view.Menu; 9 | import android.view.MenuInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.aspsine.zhihu.daily.R; 14 | 15 | /** 16 | * A simple {@link Fragment} subclass. 17 | */ 18 | public class SettingsFragment extends PreferenceFragment { 19 | 20 | 21 | public SettingsFragment() { 22 | // Required empty public constructor 23 | } 24 | 25 | @Override 26 | public void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | addPreferencesFromResource(R.xml.pref_general); 29 | } 30 | 31 | @Override 32 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 33 | Bundle savedInstanceState) { 34 | setHasOptionsMenu(true); 35 | // Inflate the layout for this fragment 36 | return super.onCreateView(inflater, container, savedInstanceState); 37 | } 38 | 39 | @Override 40 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 41 | super.onCreateOptionsMenu(menu, inflater); 42 | inflater.inflate(R.menu.menu_story, menu); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/fragment/SplashFragment.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.fragment; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.animation.Animation; 12 | import android.view.animation.AnimationUtils; 13 | import android.widget.ImageView; 14 | import android.widget.RelativeLayout; 15 | import android.widget.TextView; 16 | 17 | import com.aspsine.zhihu.daily.App; 18 | import com.aspsine.zhihu.daily.R; 19 | import com.aspsine.zhihu.daily.animation.AnimationEndListener; 20 | import com.aspsine.zhihu.daily.model.StartImage; 21 | import com.aspsine.zhihu.daily.respository.interfaces.Repository; 22 | import com.aspsine.zhihu.daily.ui.activity.GuiderActivity; 23 | import com.aspsine.zhihu.daily.util.DensityUtil; 24 | import com.aspsine.zhihu.daily.util.L; 25 | import com.aspsine.zhihu.daily.util.UIUtils; 26 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 27 | import com.nostra13.universalimageloader.core.ImageLoader; 28 | import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; 29 | 30 | /** 31 | * Created by sf on 2015/1/13. 32 | * splash page 33 | */ 34 | public class SplashFragment extends Fragment { 35 | public static final String TAG = SplashFragment.class.getSimpleName(); 36 | 37 | private TextView tvAuthor; 38 | private ImageView ivLogo; 39 | public ImageView ivSplash; 40 | public Animation mIvSplashAnim; 41 | 42 | private final DisplayImageOptions mOptions = new DisplayImageOptions.Builder() 43 | .cacheInMemory(false) 44 | .cacheOnDisk(true) 45 | .considerExifParams(true) 46 | .build(); 47 | 48 | private int mWidth; 49 | private int mHeight; 50 | 51 | @Override 52 | public void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | mIvSplashAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.splash); 55 | 56 | // if api >= 19 use themes in values-v19 has a full screen splash. 57 | mWidth = DensityUtil.getScreenWidth(getActivity()); 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 59 | mHeight = DensityUtil.getScreenHeightWithDecorations(getActivity()); 60 | } else { 61 | mHeight = DensityUtil.getScreenHeight(getActivity()); 62 | } 63 | L.i(TAG, "screen height = " + mHeight); 64 | } 65 | 66 | @Override 67 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 68 | return inflater.inflate(R.layout.fragment_splash, container, false); 69 | } 70 | 71 | @Override 72 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 73 | super.onViewCreated(view, savedInstanceState); 74 | tvAuthor = (TextView) view.findViewById(R.id.tvAuthor); 75 | ivSplash = (ImageView) view.findViewById(R.id.splash); 76 | ivLogo = (ImageView) view.findViewById(R.id.ivLogo); 77 | 78 | // if api >= 19 use themes in values-v19 has a full screen splash. 79 | // so we need relayout the tvAuthor and ivLogo by set a bigger marginBottom 80 | int navigationBarHeight = UIUtils.getNavigationBarHeight(getActivity()); 81 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 82 | 83 | int tvMarginBottom = navigationBarHeight + DensityUtil.dip2px(getActivity(), 8); 84 | int logoMarginBottom = navigationBarHeight + DensityUtil.dip2px(getActivity(), 48); 85 | ((RelativeLayout.LayoutParams) tvAuthor.getLayoutParams()).setMargins(0, 0, 0, tvMarginBottom); 86 | ((RelativeLayout.LayoutParams) ivLogo.getLayoutParams()).setMargins(0, 0, 0, logoMarginBottom); 87 | 88 | tvAuthor.requestLayout(); 89 | ivLogo.requestLayout(); 90 | } 91 | } 92 | 93 | @Override 94 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 95 | super.onActivityCreated(savedInstanceState); 96 | mIvSplashAnim.setAnimationListener(animListener); 97 | refresh(); 98 | } 99 | 100 | @Override 101 | public void onDetach() { 102 | super.onDetach(); 103 | mIvSplashAnim = null; 104 | } 105 | 106 | private final Animation.AnimationListener animListener = new AnimationEndListener() { 107 | @Override 108 | public void onAnimationEnd(Animation animation) { 109 | ivSplash.setVisibility(View.GONE); 110 | ((GuiderActivity) getActivity()).intentToMainActivity(); 111 | } 112 | }; 113 | 114 | private void refresh() { 115 | 116 | App.getRepository().getStartImage(mWidth, mHeight, mOptions, new Repository.Callback() { 117 | @Override 118 | public void success(StartImage startImage, boolean outDate) { 119 | tvAuthor.setText(startImage.getText()); 120 | ImageLoader.getInstance().displayImage(startImage.getImg(), ivSplash, mOptions, new SimpleImageLoadingListener(){ 121 | @Override 122 | public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { 123 | super.onLoadingComplete(imageUri, view, loadedImage); 124 | ivSplash.startAnimation(mIvSplashAnim); 125 | } 126 | }); 127 | 128 | } 129 | 130 | @Override 131 | public void failure(Exception e) { 132 | L.i(TAG, "default image."); 133 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 134 | ivSplash.setBackground(getResources().getDrawable(R.drawable.bg_splash)); 135 | }else{ 136 | ivSplash.setBackgroundDrawable(getResources().getDrawable(R.drawable.bg_splash)); 137 | } 138 | ivSplash.setAnimation(mIvSplashAnim); 139 | tvAuthor.setText(getResources().getString(R.string.splash_text)); 140 | e.printStackTrace(); 141 | } 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/fragment/ThemeStoriesFragment.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Toast; 11 | 12 | import com.aspsine.zhihu.daily.App; 13 | import com.aspsine.zhihu.daily.R; 14 | import com.aspsine.zhihu.daily.model.Theme; 15 | import com.aspsine.zhihu.daily.respository.interfaces.Repository; 16 | import com.aspsine.zhihu.daily.ui.adapter.ThemeStoriesAdapter; 17 | import com.aspsine.zhihu.daily.ui.widget.LoadMoreRecyclerView; 18 | import com.aspsine.zhihu.daily.util.L; 19 | 20 | /** 21 | * Created by Aspsine on 2015/3/25. 22 | */ 23 | public class ThemeStoriesFragment extends BaseFragment { 24 | public static final String TAG = ThemeStoriesFragment.class.getSimpleName(); 25 | 26 | private SwipeRefreshLayout swipeRefreshLayout; 27 | 28 | private LoadMoreRecyclerView recyclerView; 29 | 30 | private ThemeStoriesAdapter mAdapter; 31 | 32 | private String mThemeId; 33 | 34 | private String mLastStoryId; 35 | 36 | /** 37 | * Flag to indicate whither the data is successful loaded 38 | */ 39 | private boolean isDataLoaded; 40 | 41 | @Override 42 | public void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | if (savedInstanceState == null) { 45 | mAdapter = new ThemeStoriesAdapter(); 46 | } 47 | } 48 | 49 | @Override 50 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 51 | L.i(TAG, getThemeNumber() + " : " + getThemeId()); 52 | return inflater.inflate(R.layout.fragment_theme_stories, container, false); 53 | } 54 | 55 | @Override 56 | public void onViewCreated(View view, Bundle savedInstanceState) { 57 | super.onViewCreated(view, savedInstanceState); 58 | swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout); 59 | recyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recyclerView); 60 | recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 61 | // TODO adapter 62 | recyclerView.setAdapter(mAdapter); 63 | swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_dark, android.R.color.holo_blue_light, android.R.color.holo_green_light, android.R.color.holo_green_light); 64 | swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 65 | @Override 66 | public void onRefresh() { 67 | refresh(); 68 | } 69 | }); 70 | 71 | recyclerView.setOnLoadMoreListener(new LoadMoreRecyclerView.onLoadMoreListener() { 72 | @Override 73 | public void onLoadMore() { 74 | recyclerView.setLoadingMore(true); 75 | loadMore(); 76 | } 77 | 78 | @Override 79 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 80 | 81 | } 82 | }); 83 | } 84 | 85 | @Override 86 | public void onActivityCreated(Bundle savedInstanceState) { 87 | super.onActivityCreated(savedInstanceState); 88 | mThemeId = getThemeId(); 89 | swipeRefreshLayout.post(new Runnable() { 90 | @Override 91 | public void run() { 92 | if (!isDataLoaded) { 93 | swipeRefreshLayout.setRefreshing(true); 94 | refresh(); 95 | } 96 | } 97 | }); 98 | } 99 | 100 | @Override 101 | public void onDetach() { 102 | super.onDetach(); 103 | } 104 | 105 | private void refresh() { 106 | isDataLoaded = false; 107 | 108 | App.getRepository().getTheme(mThemeId, new Repository.Callback() { 109 | @Override 110 | public void success(Theme theme, boolean outDate) { 111 | isDataLoaded = true; 112 | swipeRefreshLayout.setRefreshing(false); 113 | if (theme != null && mAdapter != null) { 114 | if (theme.getStories().size() > 0) { 115 | mLastStoryId = theme.getStories().get(theme.getStories().size() - 1).getId(); 116 | } 117 | L.i(TAG, "last story id: " + mLastStoryId); 118 | mAdapter.setTheme(theme); 119 | } 120 | } 121 | 122 | @Override 123 | public void failure(Exception e) { 124 | isDataLoaded = false; 125 | swipeRefreshLayout.setRefreshing(false); 126 | Toast.makeText(getActivity(), "refresh error", Toast.LENGTH_SHORT).show(); 127 | e.printStackTrace(); 128 | } 129 | }); 130 | } 131 | 132 | private void loadMore() { 133 | 134 | App.getRepository().getThemeBeforeStory(mThemeId, mLastStoryId, new Repository.Callback() { 135 | @Override 136 | public void success(Theme theme, boolean outDate) { 137 | recyclerView.setLoadingMore(false); 138 | if (theme != null && mAdapter != null) { 139 | if (theme.getStories().size() > 0) { 140 | mLastStoryId = theme.getStories().get(theme.getStories().size() - 1).getId(); 141 | mAdapter.appendStories(theme.getStories()); 142 | } 143 | } 144 | } 145 | 146 | @Override 147 | public void failure(Exception e) { 148 | recyclerView.setLoadingMore(false); 149 | Toast.makeText(getActivity(), "load more error", Toast.LENGTH_SHORT).show(); 150 | e.printStackTrace(); 151 | } 152 | }); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/AvatarsView.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.view.MotionEvent; 7 | import android.widget.HorizontalScrollView; 8 | import android.widget.ImageView; 9 | import android.widget.LinearLayout; 10 | import android.widget.TextView; 11 | 12 | import com.aspsine.zhihu.daily.R; 13 | import com.aspsine.zhihu.daily.util.DensityUtil; 14 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 15 | import com.nostra13.universalimageloader.core.ImageLoader; 16 | 17 | import java.util.List; 18 | 19 | import butterknife.ButterKnife; 20 | import butterknife.InjectView; 21 | 22 | /** 23 | * Created by aspsine on 15-3-28. 24 | */ 25 | public class AvatarsView extends HorizontalScrollView { 26 | @InjectView(R.id.title) 27 | TextView title; 28 | @InjectView(R.id.llAvatarContainer) 29 | LinearLayout llAvatarContainer; 30 | private DisplayImageOptions mOptions; 31 | 32 | public AvatarsView(Context context) { 33 | this(context, null); 34 | } 35 | 36 | public AvatarsView(Context context, AttributeSet attrs) { 37 | this(context, attrs, 0); 38 | } 39 | 40 | public AvatarsView(Context context, AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | init(); 43 | } 44 | 45 | private void init() { 46 | setOverScrollMode(OVER_SCROLL_NEVER); 47 | LayoutInflater.from(getContext()).inflate(R.layout.view_avatars, this, true); 48 | ButterKnife.inject(this); 49 | mOptions = new DisplayImageOptions.Builder() 50 | .showImageOnLoading(R.drawable.comment_avatar) 51 | .showImageForEmptyUri(R.drawable.comment_avatar) 52 | .showImageOnFail(R.drawable.comment_avatar) 53 | .displayer(new CircleBitmapDisplayer()) 54 | .cacheInMemory(true) 55 | .cacheOnDisk(true) 56 | .considerExifParams(true) 57 | .build(); 58 | } 59 | 60 | public void bindData(String name, List images) { 61 | title.setText(name); 62 | int hw = DensityUtil.dip2px(getContext(), 36); 63 | int margin = DensityUtil.dip2px(getContext(), 8); 64 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(hw, hw); 65 | lp.setMargins(margin, margin, margin, margin); 66 | llAvatarContainer.removeAllViews(); 67 | if (images != null && images.size() > 0) { 68 | for (String url : images) { 69 | ImageView imageView = new ImageView(getContext()); 70 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 71 | imageView.setLayoutParams(lp); 72 | llAvatarContainer.addView(imageView); 73 | ImageLoader.getInstance().displayImage(url, imageView, mOptions); 74 | } 75 | } 76 | } 77 | 78 | 79 | private float mDownX; 80 | private float mDownY; 81 | @Override 82 | public boolean dispatchTouchEvent(MotionEvent ev) { 83 | switch (ev.getAction()) { 84 | case MotionEvent.ACTION_DOWN: 85 | mDownX = ev.getX(); 86 | mDownY = ev.getY(); 87 | getParent().requestDisallowInterceptTouchEvent(true); 88 | break; 89 | case MotionEvent.ACTION_CANCEL: 90 | case MotionEvent.ACTION_UP: 91 | getParent().requestDisallowInterceptTouchEvent(false); 92 | break; 93 | case MotionEvent.ACTION_MOVE: 94 | if (Math.abs(ev.getX() - mDownX) > Math.abs(ev.getY() - mDownY)) { 95 | 96 | //不允许拦截事件,自己处理 97 | getParent().requestDisallowInterceptTouchEvent(true); 98 | } else { 99 | getParent().requestDisallowInterceptTouchEvent(false); 100 | } 101 | break; 102 | } 103 | return super.dispatchTouchEvent(ev); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/CheckableLinearLayout.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Checkable; 10 | import android.widget.LinearLayout; 11 | 12 | /** 13 | * Created by Aspsine on 2015/3/23. 14 | */ 15 | public class CheckableLinearLayout extends LinearLayout implements Checkable { 16 | 17 | private boolean mChecked; 18 | 19 | private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; 20 | 21 | public CheckableLinearLayout(Context context) { 22 | this(context, null); 23 | } 24 | 25 | public CheckableLinearLayout(Context context, AttributeSet attrs) { 26 | this(context, attrs, 0); 27 | } 28 | 29 | public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | init(); 32 | } 33 | 34 | private void init() { 35 | // setClickable(true); 36 | // if (getBackground() == null) { 37 | // setBackgroundResource(R.drawable.mca__list_item_selector); 38 | // } 39 | } 40 | 41 | /**********************/ 42 | /** Handle clicks **/ 43 | /** 44 | * ****************** 45 | */ 46 | 47 | @Override 48 | public boolean performClick() { 49 | toggle(); 50 | return super.performClick(); 51 | } 52 | 53 | @Override 54 | public boolean onInterceptTouchEvent(MotionEvent ev) { 55 | return onTouchEvent(ev); 56 | } 57 | 58 | /**************************/ 59 | /** Checkable **/ 60 | /** 61 | * ********************** 62 | */ 63 | 64 | public void toggle() { 65 | setChecked(!mChecked); 66 | } 67 | 68 | public boolean isChecked() { 69 | return mChecked; 70 | } 71 | 72 | public void setChecked(boolean checked) { 73 | if (mChecked != checked) { 74 | mChecked = checked; 75 | refreshDrawableState(); 76 | setCheckedRecursive(this, checked); 77 | } 78 | } 79 | 80 | private void setCheckedRecursive(ViewGroup parent, boolean checked) { 81 | int count = parent.getChildCount(); 82 | for (int i = 0; i < count; i++) { 83 | View v = parent.getChildAt(i); 84 | if (v instanceof Checkable) { 85 | ((Checkable) v).setChecked(checked); 86 | } 87 | 88 | if (v instanceof ViewGroup) { 89 | setCheckedRecursive((ViewGroup) v, checked); 90 | } 91 | } 92 | } 93 | 94 | /**************************/ 95 | /** Drawable States **/ 96 | /** 97 | * ********************** 98 | */ 99 | 100 | @Override 101 | protected int[] onCreateDrawableState(int extraSpace) { 102 | final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 103 | if (isChecked()) { 104 | mergeDrawableStates(drawableState, CHECKED_STATE_SET); 105 | } 106 | return drawableState; 107 | } 108 | 109 | @Override 110 | protected void drawableStateChanged() { 111 | super.drawableStateChanged(); 112 | 113 | Drawable drawable = getBackground(); 114 | if (drawable != null) { 115 | int[] myDrawableState = getDrawableState(); 116 | drawable.setState(myDrawableState); 117 | invalidate(); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/LoadMoreRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | 8 | import com.aspsine.zhihu.daily.util.L; 9 | 10 | /** 11 | * Created by Aspsine on 2015/3/20. 12 | *

13 | * be careful! current version just support LinearLayoutManager 14 | *

15 | * Version 1.0 beta 16 | */ 17 | public class LoadMoreRecyclerView extends RecyclerView { 18 | private static final String TAG = LoadMoreRecyclerView.class.getSimpleName(); 19 | private boolean mIsLoadingMore = false; 20 | private onLoadMoreListener mListener; 21 | 22 | public LoadMoreRecyclerView(Context context) { 23 | this(context, null); 24 | } 25 | 26 | public LoadMoreRecyclerView(Context context, AttributeSet attrs) { 27 | super(context, attrs, 0); 28 | } 29 | 30 | public LoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) { 31 | super(context, attrs, defStyle); 32 | } 33 | 34 | void init() { 35 | 36 | this.setOnScrollListener(new onLoadMoreScrollListener()); 37 | } 38 | 39 | public static class onLoadMoreScrollListener extends OnScrollListener { 40 | 41 | @Override 42 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 43 | LoadMoreRecyclerView view = (LoadMoreRecyclerView) recyclerView; 44 | onLoadMoreListener onLoadMoreListener = view.getOnLoadMoreListener(); 45 | 46 | onLoadMoreListener.onScrolled(recyclerView, dx, dy); 47 | 48 | LinearLayoutManager layoutManager = (LinearLayoutManager) view.getLayoutManager(); 49 | int lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition(); 50 | int itemCount = layoutManager.getItemCount(); 51 | if (lastVisibleItem >= itemCount - 1 && !view.getLoadingMore()) { 52 | onLoadMoreListener.onLoadMore(); 53 | L.i(TAG, "load more: lastVisibleItem = " + lastVisibleItem + ", itemCount" + itemCount); 54 | } else { 55 | super.onScrolled(recyclerView, dx, dy); 56 | } 57 | } 58 | } 59 | 60 | 61 | public void setOnLoadMoreListener(onLoadMoreListener listener) { 62 | this.mListener = listener; 63 | init(); 64 | } 65 | 66 | public onLoadMoreListener getOnLoadMoreListener() { 67 | return this.mListener; 68 | } 69 | 70 | public void setLoadingMore(boolean isLoading) { 71 | mIsLoadingMore = isLoading; 72 | } 73 | 74 | public boolean getLoadingMore() { 75 | return mIsLoadingMore; 76 | } 77 | 78 | public interface onLoadMoreListener { 79 | public void onLoadMore(); 80 | 81 | public void onScrolled(RecyclerView recyclerView, int dx, int dy); 82 | } 83 | 84 | @Override 85 | public void setLayoutManager(LayoutManager layout) { 86 | if (layout instanceof LinearLayoutManager) { 87 | super.setLayoutManager(layout); 88 | } else { 89 | throw new IllegalArgumentException("LoadMoreRecyclerView must have a LinearLayoutManager!"); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/MySwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * Created by Aspsine on 2015/3/11. 10 | */ 11 | public class MySwipeRefreshLayout extends SwipeRefreshLayout { 12 | private float xDistance, yDistance, xLast, yLast; 13 | 14 | public MySwipeRefreshLayout(Context context) { 15 | super(context); 16 | } 17 | 18 | public MySwipeRefreshLayout(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | } 21 | 22 | @Override 23 | public boolean onInterceptTouchEvent(MotionEvent ev) { 24 | 25 | switch (ev.getAction()) { 26 | case MotionEvent.ACTION_DOWN: 27 | xDistance = yDistance = 0f; 28 | xLast = ev.getX(); 29 | yLast = ev.getY(); 30 | break; 31 | case MotionEvent.ACTION_UP: 32 | break; 33 | case MotionEvent.ACTION_MOVE: 34 | final float curX = ev.getX(); 35 | final float curY = ev.getY(); 36 | xDistance += Math.abs(curX - xLast); 37 | yDistance += Math.abs(curY - yLast); 38 | xLast = curX; 39 | yLast = curY; 40 | if (xDistance > yDistance) { 41 | return false; 42 | } 43 | } 44 | return super.onInterceptTouchEvent(ev); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/MyViewPager.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.support.v4.view.PagerAdapter; 7 | import android.support.v4.view.ViewPager; 8 | import android.util.AttributeSet; 9 | import android.view.MotionEvent; 10 | 11 | import com.aspsine.zhihu.daily.util.L; 12 | 13 | /** 14 | * Created by Aspsine on 2015/3/11. 15 | */ 16 | public class MyViewPager extends ViewPager { 17 | private static final String TAG = MyViewPager.class.getSimpleName(); 18 | private static final int WHAT_SCROLL = 0; 19 | private long mDelayTime = 5000; 20 | 21 | private float mDownX; 22 | private float mDownY; 23 | 24 | private boolean isAutoScroll; 25 | public boolean isStopByTouch; 26 | 27 | public MyViewPager(Context context) { 28 | this(context, null); 29 | } 30 | 31 | public MyViewPager(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | } 34 | 35 | @Override 36 | public boolean dispatchTouchEvent(MotionEvent ev) { 37 | switch (ev.getAction()) { 38 | case MotionEvent.ACTION_DOWN: 39 | mDownX = ev.getX(); 40 | mDownY = ev.getY(); 41 | getParent().requestDisallowInterceptTouchEvent(true); 42 | break; 43 | case MotionEvent.ACTION_CANCEL: 44 | case MotionEvent.ACTION_UP: 45 | getParent().requestDisallowInterceptTouchEvent(false); 46 | break; 47 | case MotionEvent.ACTION_MOVE: 48 | if (Math.abs(ev.getX() - mDownX) > Math.abs(ev.getY() - mDownY)) { 49 | 50 | //不允许拦截事件,自己处理 51 | getParent().requestDisallowInterceptTouchEvent(true); 52 | } else { 53 | getParent().requestDisallowInterceptTouchEvent(false); 54 | } 55 | break; 56 | } 57 | if (ev.getAction() == MotionEvent.ACTION_DOWN) { 58 | stopAutoScroll(); 59 | isStopByTouch = true; 60 | } else if (ev.getAction() == MotionEvent.ACTION_UP && isStopByTouch) { 61 | startAutoScroll(); 62 | } 63 | return super.dispatchTouchEvent(ev); 64 | } 65 | 66 | public void setDelayTime(int delayTime) { 67 | this.mDelayTime = delayTime; 68 | } 69 | 70 | 71 | public void startAutoScroll() { 72 | isAutoScroll = true; 73 | sendScrollMessage(mDelayTime); 74 | } 75 | 76 | public void stopAutoScroll() { 77 | isAutoScroll = false; 78 | handler.removeMessages(WHAT_SCROLL); 79 | } 80 | 81 | public boolean isAutoScrolling() { 82 | return isAutoScroll; 83 | } 84 | 85 | private void sendScrollMessage(long delayTimeInMills) { 86 | if (isAutoScroll) { 87 | handler.removeMessages(WHAT_SCROLL); 88 | handler.sendEmptyMessageDelayed(WHAT_SCROLL, delayTimeInMills); 89 | } 90 | } 91 | 92 | private void scrollOnce() { 93 | PagerAdapter adapter = getAdapter(); 94 | int currentItem = getCurrentItem(); 95 | int count; 96 | if (adapter == null || (count = adapter.getCount()) < 1) { 97 | stopAutoScroll(); 98 | return; 99 | } 100 | if (currentItem < count) { 101 | currentItem++; 102 | } 103 | if (currentItem == count) { 104 | currentItem = 0; 105 | } 106 | L.i(TAG, currentItem + ""); 107 | setCurrentItem(currentItem); 108 | } 109 | 110 | private final Handler handler = new Handler() { 111 | @Override 112 | public void handleMessage(Message msg) { 113 | super.handleMessage(msg); 114 | if (msg.what == WHAT_SCROLL) { 115 | scrollOnce(); 116 | sendScrollMessage(mDelayTime); 117 | } 118 | } 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/PageIndicator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Patrik Akerfeldt 3 | * Copyright (C) 2011 Jake Wharton 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.aspsine.zhihu.daily.ui.widget; 19 | 20 | import android.support.v4.view.ViewPager; 21 | 22 | /** 23 | * Copyright 2012 Jake Wharton 24 | * Copyright 2011 Patrik Åkerfeldt 25 | * Copyright 2011 Francisco Figueiredo Jr. 26 | *

27 | * Licensed under the Apache License, Version 2.0 (the "License"); 28 | * you may not use this file except in compliance with the License. 29 | * You may obtain a copy of the License at 30 | * http://www.apache.org/licenses/LICENSE-2.0 31 | * Unless required by applicable law or agreed to in writing, software 32 | * distributed under the License is distributed on an "AS IS" BASIS, 33 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | * See the License for the specific language governing permissions and 35 | * limitations under the License. 36 | *

37 | *

38 | * A PageIndicator is responsible to show an visual indicator on the total views 39 | * number and the current visible view. 40 | */ 41 | public interface PageIndicator extends ViewPager.OnPageChangeListener { 42 | /** 43 | * Bind the indicator to a ViewPager. 44 | * 45 | * @param view 46 | */ 47 | void setViewPager(ViewPager view); 48 | 49 | /** 50 | * Bind the indicator to a ViewPager. 51 | * 52 | * @param view 53 | * @param initialPosition 54 | */ 55 | void setViewPager(ViewPager view, int initialPosition); 56 | 57 | /** 58 | *

Set the current page of both the ViewPager and indicator.

59 | *

60 | *

This must be used if you need to set the page before 61 | * the views are drawn on screen (e.g., default start page).

62 | * 63 | * @param item 64 | */ 65 | void setCurrentItem(int item); 66 | 67 | /** 68 | * Set a page change listener which will receive forwarded events. 69 | * 70 | * @param listener 71 | */ 72 | void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); 73 | 74 | /** 75 | * Notify the indicator that the fragment list has changed. 76 | */ 77 | void notifyDataSetChanged(); 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/ui/widget/StoryHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.ui.widget; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.RelativeLayout; 11 | import android.widget.TextView; 12 | 13 | import com.aspsine.zhihu.daily.R; 14 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 15 | import com.nostra13.universalimageloader.core.ImageLoader; 16 | 17 | import butterknife.ButterKnife; 18 | import butterknife.InjectView; 19 | 20 | /** 21 | * Created by Aspsine on 2015/3/24. 22 | */ 23 | public class StoryHeaderView extends RelativeLayout { 24 | @InjectView(R.id.image) 25 | ImageView image; 26 | @InjectView(R.id.title) 27 | TextView title; 28 | @InjectView(R.id.tvAuthor) 29 | TextView author; 30 | 31 | public StoryHeaderView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public StoryHeaderView(Context context, AttributeSet attrs) { 36 | this(context, attrs, 0); 37 | } 38 | 39 | public StoryHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | init(); 42 | } 43 | 44 | private void init() { 45 | setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.view_header_story_height))); 46 | LayoutInflater.from(this.getContext()).inflate(R.layout.view_header_story, this, true); 47 | ButterKnife.inject(this); 48 | } 49 | 50 | public void bindData(String title, String author, String url) { 51 | DisplayImageOptions options = new DisplayImageOptions.Builder() 52 | .cacheInMemory(true) 53 | .cacheOnDisk(true) 54 | .considerExifParams(true) 55 | .build(); 56 | this.bindData(title, author, url, options); 57 | } 58 | 59 | public void bindData(String title, String author, String url, DisplayImageOptions options) { 60 | this.title.setText(title); 61 | if (TextUtils.isEmpty(author)) { 62 | this.author.setVisibility(View.GONE); 63 | } else { 64 | this.author.setVisibility(View.VISIBLE); 65 | this.author.setText(author); 66 | } 67 | ImageLoader.getInstance().displayImage(url, image, options); 68 | } 69 | 70 | public static StoryHeaderView newInstance(ViewGroup container) { 71 | return new StoryHeaderView(container.getContext()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import java.text.DateFormat; 4 | import java.text.ParseException; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | import java.util.concurrent.CancellationException; 9 | 10 | /** 11 | * Created by Aspsine on 2015/2/28. 12 | */ 13 | public class DateUtils { 14 | 15 | public static String getDate(Date date, String format) { 16 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format); 17 | return String.valueOf(simpleDateFormat.format(date)); 18 | } 19 | 20 | public static boolean isToday(String date) { 21 | String today = new SimpleDateFormat("yyyyMMdd").format(Calendar.getInstance().getTime()); 22 | if (date.equals(today)) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | 28 | public static String getMainPageDate(String date) { 29 | String mData = ""; 30 | try { 31 | Date tmpDate = new SimpleDateFormat("yyyyMMdd").parse(date); 32 | mData = DateFormat.getDateInstance(DateFormat.FULL).format(tmpDate); 33 | } catch (ParseException e) { 34 | e.printStackTrace(); 35 | } 36 | 37 | String thisYear = Calendar.getInstance().get(Calendar.YEAR) + "年"; 38 | if (mData.startsWith(thisYear)) { 39 | return mData.replace(thisYear, ""); 40 | } 41 | return mData; 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/DensityUtil.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.hardware.display.DisplayManager; 6 | import android.util.DisplayMetrics; 7 | import android.view.Display; 8 | import android.view.WindowManager; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | 12 | /** 13 | * Created by aspsine on 15-3-13. 14 | */ 15 | public class DensityUtil { 16 | 17 | public static int getScreenWidth(Context context) { 18 | DisplayMetrics dm = new DisplayMetrics(); 19 | ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(dm); 20 | return dm.widthPixels; 21 | } 22 | 23 | public static int getScreenHeight(Context context) { 24 | DisplayMetrics dm = new DisplayMetrics(); 25 | ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(dm); 26 | return dm.heightPixels; 27 | } 28 | 29 | public static int getScreenHeightWithDecorations(Context context) { 30 | int heightPixels; 31 | WindowManager w = ((Activity) context).getWindowManager(); 32 | Display d = w.getDefaultDisplay(); 33 | android.graphics.Point realSize = new android.graphics.Point(); 34 | try { 35 | Display.class.getMethod("getRealSize", android.graphics.Point.class).invoke(d, realSize); 36 | } catch (IllegalAccessException e) { 37 | e.printStackTrace(); 38 | } catch (InvocationTargetException e) { 39 | e.printStackTrace(); 40 | } catch (NoSuchMethodException e) { 41 | e.printStackTrace(); 42 | } 43 | heightPixels = realSize.y; 44 | return heightPixels; 45 | } 46 | 47 | 48 | public static int dip2px(Context context, float dpValue) { 49 | 50 | final float scale = context.getResources().getDisplayMetrics().density; 51 | return (int) (dpValue * scale + 0.5f); 52 | 53 | } 54 | 55 | public static int px2dip(Context context, float pxValue) { 56 | final float scale = context.getResources().getDisplayMetrics().density; 57 | return (int) (pxValue / scale + 0.5f); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Created by Aspsine on 2015/3/31. 10 | */ 11 | public class FileUtils { 12 | 13 | private static final String HTTP_CACHE_DIR = "http"; 14 | 15 | public static final File getHttpCacheDir(Context context) { 16 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 17 | return new File(context.getExternalCacheDir(), HTTP_CACHE_DIR); 18 | } 19 | return new File(context.getCacheDir(), HTTP_CACHE_DIR); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/IntentUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | 6 | import com.aspsine.zhihu.daily.R; 7 | import com.aspsine.zhihu.daily.model.Story; 8 | import com.aspsine.zhihu.daily.ui.activity.NavigationDrawerActivity; 9 | import com.aspsine.zhihu.daily.ui.activity.StoryActivity; 10 | 11 | /** 12 | * Created by Aspsine on 2015/3/20. 13 | */ 14 | public class IntentUtils { 15 | public static final String EXTRA_STORY_ID = "extra_story_id"; 16 | 17 | public static final void intentToMainActivity(Activity activity) { 18 | Intent intent = new Intent(activity, NavigationDrawerActivity.class); 19 | activity.startActivity(intent); 20 | activity.finish(); 21 | activity.overridePendingTransition(android.support.v7.appcompat.R.anim.abc_fade_in, android.support.v7.appcompat.R.anim.abc_fade_out); 22 | } 23 | 24 | public static final void intentToStoryActivity(Activity activity, Story story) { 25 | Intent intent = new Intent(activity, StoryActivity.class); 26 | intent.putExtra(EXTRA_STORY_ID, String.valueOf(story.getId())); 27 | activity.startActivity(intent); 28 | } 29 | 30 | public static final void share(Activity activity, Story story) { 31 | StringBuilder sb = new StringBuilder(); 32 | sb.append(story.getTitle()).append(" ") 33 | .append(activity.getString(R.string.share_link)) 34 | .append(story.getShareUrl()) 35 | .append(" ") 36 | .append(activity.getString(R.string.share_from)); 37 | Intent intent = new Intent(Intent.ACTION_SEND); 38 | intent.setType("text/plain"); 39 | intent.putExtra(Intent.EXTRA_TITLE, story.getTitle()); 40 | intent.putExtra(Intent.EXTRA_TEXT, sb.toString()); 41 | activity.startActivity(intent); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/L.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.util.Log; 4 | 5 | import com.aspsine.zhihu.daily.BuildConfig; 6 | 7 | /** 8 | * Created by aspsine on 15-3-13. 9 | */ 10 | public class L{ 11 | 12 | public static int i(String tag, String msg){ 13 | if (BuildConfig.DEBUG) { 14 | return Log.i(tag, msg); 15 | } 16 | return -1; 17 | } 18 | 19 | public static int e(String tag, String msg){ 20 | if (BuildConfig.DEBUG) { 21 | return Log.e(tag, msg); 22 | } 23 | return -1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/ListUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by aspsine on 15-4-6. 7 | */ 8 | public class ListUtils { 9 | 10 | public static final boolean isEmpty(List list) { 11 | if (list != null && list.size() > 0) { 12 | return false; 13 | } 14 | return true; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/NetWorkUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * Created by Aspsine on 2015/3/13. 9 | */ 10 | public class NetWorkUtils { 11 | public static final boolean isNetWorkAvailable(Context context) { 12 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 13 | if (cm != null) { 14 | NetworkInfo[] info = cm.getAllNetworkInfo(); 15 | if (info != null) { 16 | for (int i = 0; i < info.length; i++) { 17 | if (info[i].getState() == NetworkInfo.State.CONNECTED) { 18 | return true; 19 | } 20 | } 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/ScrollPullDownHelper.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | 4 | import java.util.LinkedList; 5 | import java.util.Queue; 6 | 7 | /** 8 | * Created by aspsine on 15-3-24. thanks chentian. 9 | * Detect several latest moves to ensure a smooth showing-hiding toolbar effect 10 | * 11 | * @author chentian 12 | */ 13 | public class ScrollPullDownHelper { 14 | 15 | private static final int PULLING_DOWN_TIME_MAX = 8; 16 | private static final int PULLING_DOWN_TIME_THRESHOLD = 6; 17 | 18 | private int lastScrollY; 19 | private Queue latestPullingDown; 20 | 21 | public ScrollPullDownHelper() { 22 | lastScrollY = 0; 23 | latestPullingDown = new LinkedList<>(); 24 | } 25 | 26 | /** 27 | * Call this when scroll changed 28 | * 29 | * @return true if user is pulling down 30 | */ 31 | public boolean onScrollChanged(int scrollY) { 32 | boolean isPullingDownNow = scrollY < lastScrollY; 33 | latestPullingDown.offer(isPullingDownNow); 34 | if (latestPullingDown.size() > PULLING_DOWN_TIME_MAX) { 35 | latestPullingDown.poll(); 36 | } 37 | lastScrollY = scrollY; 38 | 39 | return getPullingDownTime() >= PULLING_DOWN_TIME_THRESHOLD; 40 | } 41 | 42 | private int getPullingDownTime() { 43 | int result = 0; 44 | for (Boolean isPullingDown : latestPullingDown) { 45 | if (isPullingDown) { 46 | result++; 47 | } 48 | } 49 | return result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/SharedPrefUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | /** 8 | * Created by sf on 2015/1/13. 9 | */ 10 | public class SharedPrefUtils { 11 | public static final String TAG = SharedPrefUtils.class.getSimpleName(); 12 | 13 | /** 14 | * Boolean indicating whether launch the app first time 15 | */ 16 | public static final String SHARED_PREF_IS_FIRST_LAUNCH = "shared_pref_is_first_launch"; 17 | 18 | /** 19 | * Boolean indicating whether user learned the drawer 20 | *

21 | * Per the design guidelines, you should show the drawer on launch until the user manually 22 | * expands it. This shared preference tracks this. 23 | */ 24 | public static final String SHARED_PREF_IS_USER_LEARNED_DRAWER = "shared_pref_is_user_learned_drawer"; 25 | 26 | 27 | public static final String SHARED_PREF_SPLASH_JSON = "shared_pref-splash_json"; 28 | 29 | public static SharedPreferences getDefaultSharedPreferences(Context context) { 30 | return PreferenceManager.getDefaultSharedPreferences(context); 31 | } 32 | 33 | /** 34 | * whether launch the app first time 35 | * 36 | * @param context 37 | * @return 38 | */ 39 | public static boolean isFirstLaunch(final Context context) { 40 | SharedPreferences sp = getDefaultSharedPreferences(context); 41 | return sp.getBoolean(SHARED_PREF_IS_FIRST_LAUNCH, true); 42 | } 43 | 44 | /** 45 | * mark app launched 46 | * 47 | * @param context 48 | */ 49 | public static void markFirstLaunch(final Context context) { 50 | SharedPreferences sp = getDefaultSharedPreferences(context); 51 | sp.edit().putBoolean(SHARED_PREF_IS_FIRST_LAUNCH, false).apply(); 52 | } 53 | 54 | public static boolean isUserLearnedDrawer(final Context context) { 55 | SharedPreferences sp = getDefaultSharedPreferences(context); 56 | return sp.getBoolean(SHARED_PREF_IS_USER_LEARNED_DRAWER, false); 57 | } 58 | 59 | public static void markUserLearnedDrawer(Context context) { 60 | SharedPreferences sp = getDefaultSharedPreferences(context); 61 | sp.edit().putBoolean(SHARED_PREF_IS_USER_LEARNED_DRAWER, true).commit(); 62 | } 63 | 64 | public static String getSplashJson(Context context){ 65 | SharedPreferences sp = getDefaultSharedPreferences(context); 66 | return sp.getString(SHARED_PREF_SPLASH_JSON, null); 67 | } 68 | 69 | public static void setSplashJson(Context context, String jsonString){ 70 | SharedPreferences sp = getDefaultSharedPreferences(context); 71 | sp.edit().putString(SHARED_PREF_SPLASH_JSON, jsonString).commit(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/UIUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.os.Build; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * Created by Aspsine on 2015/2/25. 15 | */ 16 | public class UIUtils { 17 | public static void setAccessibilityIgnore(View view) { 18 | view.setClickable(false); 19 | view.setFocusable(false); 20 | view.setContentDescription(""); 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 22 | view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 23 | } 24 | } 25 | 26 | public static final View inflate(int resId, ViewGroup parent) { 27 | return LayoutInflater.from(parent.getContext()).inflate(resId, parent, false); 28 | } 29 | 30 | public static int getStatusBarHeight(Context context) { 31 | Resources resources = context.getResources(); 32 | int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); 33 | int height = resources.getDimensionPixelSize(resourceId); 34 | return height; 35 | } 36 | 37 | public static int getNavigationBarHeight(Context context) { 38 | Resources resources = context.getResources(); 39 | int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); 40 | int height = resources.getDimensionPixelSize(resourceId); 41 | return height; 42 | } 43 | 44 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 45 | public static boolean hasNavigationBar(Context activity) { 46 | boolean hasNavigationBar = false; 47 | Resources rs = activity.getResources(); 48 | int id = rs.getIdentifier("config_showNavigationBar", "bool", "android"); 49 | if (id > 0) { 50 | hasNavigationBar = rs.getBoolean(id); 51 | } 52 | try { 53 | Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); 54 | Method m = systemPropertiesClass.getMethod("get", String.class); 55 | String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); 56 | if ("1".equals(navBarOverride)) { 57 | hasNavigationBar = false; 58 | } else if ("0".equals(navBarOverride)) { 59 | hasNavigationBar = true; 60 | } 61 | } catch (Exception e) { 62 | L.e("UIUtils", e.toString()); 63 | } 64 | 65 | return hasNavigationBar; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/aspsine/zhihu/daily/util/WebUtils.java: -------------------------------------------------------------------------------- 1 | package com.aspsine.zhihu.daily.util; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by aspsine on 15-3-24. 7 | */ 8 | public class WebUtils { 9 | public static final String BASE_URL = "file:///android_asset/"; 10 | public static final String MIME_TYPE = "text/html"; 11 | public static final String ENCODING = "utf-8"; 12 | public static final String FAIL_URL = "http//:daily.zhihu.com/"; 13 | 14 | private static final String CSS_LINK_PATTERN = " "; 15 | private static final String NIGHT_DIV_TAG_START = "

"; 16 | private static final String NIGHT_DIV_TAG_END = "
"; 17 | 18 | private static final String DIV_IMAGE_PLACE_HOLDER="class=\"img-place-holder\""; 19 | private static final String DIV_IMAGE_PLACE_HOLDER_IGNORED = "class=\"img-place-holder-ignored\""; 20 | public static String BuildHtmlWithCss(String html, List cssUrls, boolean isNightMode) { 21 | StringBuilder result = new StringBuilder(); 22 | for (String cssUrl: cssUrls){ 23 | result.append(String.format(CSS_LINK_PATTERN,cssUrl)); 24 | } 25 | 26 | if(isNightMode){ 27 | result.append(NIGHT_DIV_TAG_START); 28 | } 29 | result.append(html.replace(DIV_IMAGE_PLACE_HOLDER, DIV_IMAGE_PLACE_HOLDER_IGNORED)); 30 | if (isNightMode){ 31 | result.append(NIGHT_DIV_TAG_END); 32 | } 33 | 34 | return result.toString(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/anim/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 13 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/dark_comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/dark_comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/home_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/home_pic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/menu_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/menu_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-hdpi/splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/dark_comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/dark_comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/home_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/home_pic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/menu_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/menu_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-mdpi/splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/dark_comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/dark_comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/home_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/home_pic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/menu_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/menu_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xhdpi/splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/dark_comment_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/dark_comment_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/home_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/home_pic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/menu_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/menu_home.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxhdpi/splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable/bg_splash.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_mask.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dark_image_small_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable/dark_image_small_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/image_small_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aspsine/Daily/94a470bc37aa0a6b5663a7f5f473c99de3d0d73e/app/src/main/res/drawable/image_small_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mask.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selected_navdrawer_item_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_guider.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_navigation_drawer.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 18 | 19 | 21 | 25 | 26 | 31 | 33 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_story.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_daily_stories.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_guide.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 16 | 21 | 22 |