├── .idea
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── me
│ │ └── mrrobot97
│ │ └── designer
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── fonts
│ │ │ └── New Walt Disney.ttf
│ ├── java
│ │ └── me
│ │ │ └── mrrobot97
│ │ │ └── designer
│ │ │ ├── Glide
│ │ │ └── CustomGlideModule.java
│ │ │ ├── MyApplication.java
│ │ │ ├── SwipeActivity
│ │ │ ├── ActivityLifeCycleHelper.java
│ │ │ ├── SwipeBackActivity.java
│ │ │ └── TouchHelper.java
│ │ │ ├── Utils
│ │ │ ├── BitmapUtils.java
│ │ │ ├── FileUtils.java
│ │ │ ├── NetUtils.java
│ │ │ ├── ScreenUtils.java
│ │ │ ├── SharedPreferencesUtils.java
│ │ │ └── StringUtils.java
│ │ │ ├── adapter
│ │ │ ├── AttachmentsAdapter.java
│ │ │ └── ShotsAdapter.java
│ │ │ ├── contracts
│ │ │ ├── BrowseContract.java
│ │ │ ├── DetailContract.java
│ │ │ └── PlayerContract.java
│ │ │ ├── customViews
│ │ │ ├── CircleImageView.java
│ │ │ ├── FullScreenView.java
│ │ │ └── HoverView.java
│ │ │ ├── model
│ │ │ ├── Attachment.java
│ │ │ ├── Comment.java
│ │ │ ├── IModel.java
│ │ │ ├── Images.java
│ │ │ ├── Links.java
│ │ │ ├── ModelImpl.java
│ │ │ ├── Shot.java
│ │ │ └── User.java
│ │ │ ├── presenter
│ │ │ ├── BrowsePresenter.java
│ │ │ ├── DetailPresenter.java
│ │ │ └── PlayerPresenter.java
│ │ │ ├── retrofit
│ │ │ ├── ApiClient.java
│ │ │ └── DribbbleService.java
│ │ │ └── view
│ │ │ ├── BaseFragment.java
│ │ │ ├── BrowseActivity.java
│ │ │ ├── DetailActivity.java
│ │ │ ├── LoginActivity.java
│ │ │ └── PlayerActivity.java
│ └── res
│ │ ├── drawable
│ │ ├── browser.png
│ │ ├── comments.png
│ │ ├── dribbble.png
│ │ ├── group.png
│ │ ├── ic_account.png
│ │ ├── ic_arrow.png
│ │ ├── ic_menu.png
│ │ ├── link.png
│ │ ├── loading.gif
│ │ ├── placeholder.png
│ │ ├── round_rectangle.xml
│ │ ├── share.png
│ │ ├── twitter.png
│ │ └── user.png
│ │ ├── layout
│ │ ├── activity_browse.xml
│ │ ├── activity_detail.xml
│ │ ├── activity_login.xml
│ │ ├── activity_player.xml
│ │ ├── author_and_shot_layout.xml
│ │ ├── comment_item_layout.xml
│ │ ├── fragment_layout.xml
│ │ ├── internet_error_view.xml
│ │ ├── recyclerview_item_layout.xml
│ │ └── user_menu_item_view.xml
│ │ ├── menu
│ │ └── menu_browse.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── icon.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── me
│ └── mrrobot97
│ └── designer
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > Dribbble是一个优秀的设计师网站,这里有上万优秀设计师为移动开发人员提供了海量精美的UI资源。Dribbble很早就开放了API,也有许多优秀的第三方客户端,本着学习的目的,我在课余时间写了这个还很粗糙的客户端。目前的功能还很简陋,用户体验也不是很完善,主要是因为Dribbble的API服务器在国外,国内加载资源很慢。不过,我会慢慢更新的,一点一点把它变得更好。
2 |
3 | [项目github地址](https://github.com/mrrobot97/Designer)
4 |
5 | 先看一下预览图:
6 |
7 | 
8 |
9 | 
10 |
11 | 
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
19 | 主要使用的技术和开源项目:
20 |
21 | ```
22 | 1. MVP设计模式。
23 | 2. RxJAVA
24 | 3. Retrofit
25 | 4. OkHttp
26 | 5. Glide
27 | ```
28 |
29 | feature:
30 |
31 | ```
32 | 1.支持页面滑动返回
33 | 2.支持离线缓存浏览
34 | 3.支持JAVA 8
35 | 4.支持Oauth2.0认证登录
36 | ```
37 |
38 | todo list:
39 |
40 | 1. ~~使用数据库离线缓存,节省客户端流量。~~ (已使用OKHttp离线缓存代替)
41 |
42 | 2. 使用palette动态改变背景色,增加美观性。
43 |
44 | 3. 使用Dribbble提供的Oauth2认证允许用户登录,并对每个设计进行评论,点赞,收藏等功能。(已初步实现Oauth2.0认证登陆)
45 |
46 | 4. 改善图片加载速度,提供友好的用户反馈。
47 |
48 | 5. 发现并消灭BUG,提高软件使用的稳定性。
49 |
50 |
51 | 欢迎fork、issue、star。
52 |
53 | # ChangeLog
54 |
55 | ## version 0.1.1
56 |
57 | ```
58 | 1.加入Oauth2.0登录入口,用户可以登录并查看自己的个人信息。
59 | 2.利用OKHttp加入离线缓存,没有网也可以愉快的玩耍了。
60 | ```
61 |
62 | ## [version 0.1.1 apk download](https://blog-1256554550.cos.ap-beijing.myqcloud.com/release0.1.1.apk)
63 |
64 | ## [version 0.1.0 apk download](https://blog-1256554550.cos.ap-beijing.myqcloud.com/Designer.apk)
65 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.1"
6 | defaultConfig {
7 | applicationId "me.mrrobot97.designer"
8 | minSdkVersion 14
9 | targetSdkVersion 24
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | jackOptions {
14 | enabled true
15 | }
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | }
28 |
29 | dependencies {
30 | compile fileTree(dir: 'libs', include: ['*.jar'])
31 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
32 | exclude group: 'com.android.support', module: 'support-annotations'
33 | })
34 | compile 'com.android.support:appcompat-v7:24.2.0'
35 | compile 'com.android.support:design:24.2.0'
36 | testCompile 'junit:junit:4.12'
37 | compile 'com.android.support:palette-v7:24.2.0'
38 | compile 'com.android.support:support-v4:24.2.0'
39 |
40 |
41 | compile 'io.reactivex:rxandroid:1.2.1'
42 | compile 'io.reactivex:rxjava:1.1.6'
43 | compile 'com.google.code.gson:gson:2.6.2'
44 | compile 'com.squareup.retrofit2:retrofit:2.1.0'
45 | compile 'com.squareup.retrofit2:converter-gson:2.1.0'
46 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
47 | compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
48 | compile 'com.squareup.okhttp3:okhttp:3.4.1'
49 | compile 'com.jakewharton:butterknife:8.4.0'
50 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
51 | compile 'com.github.bumptech.glide:glide:3.7.0'
52 |
53 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
54 | releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
55 | testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mrrobot/Library/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/me/mrrobot97/designer/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("me.mrrobot97.designer", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/New Walt Disney.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/assets/fonts/New Walt Disney.ttf
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Glide/CustomGlideModule.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Glide;
2 |
3 | import android.content.Context;
4 |
5 | import com.bumptech.glide.Glide;
6 | import com.bumptech.glide.GlideBuilder;
7 | import com.bumptech.glide.load.engine.cache.ExternalCacheDiskCacheFactory;
8 | import com.bumptech.glide.load.engine.cache.LruResourceCache;
9 | import com.bumptech.glide.module.GlideModule;
10 |
11 | /**
12 | * Created by mrrobot on 16/11/4.
13 | */
14 |
15 | public class CustomGlideModule implements GlideModule {
16 | private int cacheSize = 1024 * 1024 * 200; //200MB
17 | private int memoryCacheSize=1024*30; //30MB
18 |
19 | @Override
20 | public void applyOptions(Context context, GlideBuilder builder) {
21 | builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, cacheSize))
22 | .setMemoryCache(new LruResourceCache(memoryCacheSize));
23 | }
24 |
25 | @Override
26 | public void registerComponents(Context context, Glide glide) {
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/MyApplication.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | import com.squareup.leakcanary.LeakCanary;
7 |
8 | import me.mrrobot97.designer.SwipeActivity.ActivityLifeCycleHelper;
9 |
10 | /**
11 | * Created by mrrobot on 16/10/21.
12 | */
13 |
14 | public class MyApplication extends Application {
15 | public ActivityLifeCycleHelper getHelper() {
16 | return mHelper;
17 | }
18 |
19 | private static Context mContext;
20 |
21 | private ActivityLifeCycleHelper mHelper;
22 | @Override
23 | public void onCreate() {
24 | super.onCreate();
25 | if (LeakCanary.isInAnalyzerProcess(this)) {
26 | // This process is dedicated to LeakCanary for heap analysis.
27 | // You should not init your app in this process.
28 | return;
29 | }
30 | LeakCanary.install(this);
31 |
32 | mHelper=new ActivityLifeCycleHelper();
33 | //store all the activities
34 | registerActivityLifecycleCallbacks(mHelper);
35 | mContext=this.getApplicationContext();
36 | }
37 |
38 | public static Context getContext(){
39 | return mContext;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/SwipeActivity/ActivityLifeCycleHelper.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.SwipeActivity;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.os.Bundle;
6 |
7 | import java.util.Stack;
8 |
9 | /**
10 | * Created by mrrobot on 16/10/21.
11 | */
12 |
13 | public class ActivityLifeCycleHelper implements Application.ActivityLifecycleCallbacks {
14 | private Stack mActivities;
15 |
16 | public ActivityLifeCycleHelper(){
17 | mActivities=new Stack<>();
18 | }
19 |
20 | @Override
21 | public void onActivityCreated(Activity activity, Bundle bundle) {
22 | mActivities.add(activity);
23 | }
24 |
25 | @Override
26 | public void onActivityStarted(Activity activity) {
27 |
28 | }
29 |
30 | @Override
31 | public void onActivityResumed(Activity activity) {
32 |
33 | }
34 |
35 | @Override
36 | public void onActivityPaused(Activity activity) {
37 |
38 | }
39 |
40 | @Override
41 | public void onActivityStopped(Activity activity) {
42 |
43 | }
44 |
45 | @Override
46 | public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
47 |
48 | }
49 |
50 | @Override
51 | public void onActivityDestroyed(Activity activity) {
52 | mActivities.remove(activity);
53 | }
54 |
55 | public Activity getPreActivity(){
56 | int size=mActivities.size();
57 | if(size<2) return null;
58 | else return mActivities.get(size-2);
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/SwipeActivity/SwipeBackActivity.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.SwipeActivity;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.view.MotionEvent;
5 |
6 | /**
7 | * Created by mrrobot on 16/10/21.
8 | */
9 |
10 | public class SwipeBackActivity extends AppCompatActivity {
11 | private TouchHelper mTouchHelepr;
12 |
13 | @Override
14 | public boolean dispatchTouchEvent(MotionEvent ev) {
15 | if(mTouchHelepr==null)
16 | mTouchHelepr=new TouchHelper(getWindow());
17 | boolean consume=mTouchHelepr.processTouchEvent(ev);
18 | if(!consume) return super.dispatchTouchEvent(ev);
19 | return false;
20 | //return super.dispatchTouchEvent(ev)||mTouchHelepr.processTouchEvent(ev);
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/SwipeActivity/TouchHelper.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.SwipeActivity;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.app.Activity;
6 | import android.content.Context;
7 | import android.graphics.Canvas;
8 | import android.graphics.drawable.Drawable;
9 | import android.graphics.drawable.GradientDrawable;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.view.Window;
14 | import android.view.animation.DecelerateInterpolator;
15 | import android.widget.FrameLayout;
16 | import me.mrrobot97.designer.MyApplication;
17 |
18 | /**
19 | * Created by mrrobot on 16/10/21.
20 | */
21 |
22 | public class TouchHelper {
23 | //定义当前所处的状态
24 | private boolean isIdle=true;
25 | private boolean isSlinding=false;
26 | private boolean isAnimating=false;
27 |
28 | //左边触发的宽度
29 | private int triggerWidth=50;
30 |
31 | private int SHADOW_WIDTH=30;
32 |
33 |
34 | private Window mWindow;
35 | private ViewGroup preContentView;
36 | private ViewGroup curContentView;
37 | private ViewGroup curView;
38 | private ViewGroup preView;
39 | private Activity preActivity;
40 |
41 | private ShadowView mShadowView;
42 |
43 |
44 |
45 | public TouchHelper(Window window){
46 | mWindow=window;
47 | }
48 |
49 | private Context getContext(){
50 | return mWindow.getContext();
51 | }
52 |
53 |
54 | //决定是否拦截事件
55 | public boolean processTouchEvent(MotionEvent event){
56 | if(isAnimating) return true;
57 | float x=event.getRawX();
58 | switch (event.getAction()){
59 | case MotionEvent.ACTION_DOWN:
60 | if(x<=triggerWidth){
61 | isIdle=false;
62 | isSlinding=true;
63 | startSlide();
64 | return true;
65 | }
66 | break;
67 | case MotionEvent.ACTION_POINTER_DOWN:
68 | if(isSlinding) return true;
69 | break;
70 | case MotionEvent.ACTION_MOVE:
71 | if(isSlinding){
72 | if(event.getActionIndex()!=0) return true;
73 | sliding(x);
74 | }
75 | break;
76 | case MotionEvent.ACTION_CANCEL:
77 | case MotionEvent.ACTION_UP:
78 | if(!isSlinding) return false;
79 | int width=getContext().getResources().getDisplayMetrics().widthPixels;
80 | isAnimating=true;
81 | isSlinding=false;
82 | startAnimating(width/x<=3,x);
83 | return true;
84 | default:
85 | break;
86 | }
87 | return false;
88 | }
89 |
90 | private void startAnimating(final boolean isFinishing, float x) {
91 | int width=getContext().getResources().getDisplayMetrics().widthPixels;
92 | ValueAnimator animator=ValueAnimator.ofFloat(x,isFinishing?width:0);
93 | animator.setInterpolator(new DecelerateInterpolator());
94 | animator.setDuration(200);
95 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
96 | @Override
97 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
98 | sliding((Float) valueAnimator.getAnimatedValue());
99 | }
100 | });
101 | animator.addListener(new Animator.AnimatorListener() {
102 | @Override
103 | public void onAnimationStart(Animator animator) {
104 |
105 | }
106 |
107 | @Override
108 | public void onAnimationEnd(Animator animator) {
109 | doEndWorks(isFinishing);
110 | }
111 |
112 | @Override
113 | public void onAnimationCancel(Animator animator) {
114 |
115 | }
116 |
117 | @Override
118 | public void onAnimationRepeat(Animator animator) {
119 |
120 | }
121 | });
122 | animator.start();
123 | }
124 |
125 | private void doEndWorks(boolean isFinishing) {
126 | if(preActivity==null) return;
127 | if(isFinishing){
128 | //更改当前activity的底view为preView,防止当前activity finish时的白屏闪烁
129 | BackView view=new BackView(getContext());
130 | view.cacheView(preView);
131 | curContentView.addView(view,0);
132 | }
133 | curContentView.removeView(mShadowView);
134 | if(curContentView==null||preContentView==null) return;
135 | curContentView.removeView(preView);
136 | preContentView.addView(preView);
137 | if(isFinishing){
138 | ((Activity)getContext()).finish();
139 | ((Activity)getContext()).overridePendingTransition(0,0);
140 | }
141 | isAnimating=false;
142 | isSlinding=false;
143 | isIdle=true;
144 | preView=null;
145 | curView=null;
146 | }
147 |
148 | private void sliding(float rawX) {
149 | if(preActivity==null) return;
150 | curView.setX(rawX);
151 | preView.setX(-preView.getWidth()/3+rawX/3);
152 | mShadowView.setX(-SHADOW_WIDTH+rawX);
153 | }
154 |
155 | private void startSlide() {
156 | preActivity=((MyApplication)getContext().getApplicationContext()).getHelper().getPreActivity();
157 | if(preActivity==null) return;
158 | preContentView=(ViewGroup) preActivity.getWindow().findViewById(Window.ID_ANDROID_CONTENT);
159 | preView= (ViewGroup) preContentView.getChildAt(0);
160 | preContentView.removeView(preView);
161 | curContentView=(ViewGroup) mWindow.findViewById(Window.ID_ANDROID_CONTENT);
162 | curView= (ViewGroup) curContentView.getChildAt(0);
163 | preView.setX(-preView.getWidth()/3);
164 | //经过实际验证,addView中的index越大,则显示的越靠上,即越靠后绘制
165 | curContentView.addView(preView,0);
166 | if(mShadowView==null){
167 | mShadowView=new ShadowView(getContext());
168 | }
169 | FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(SHADOW_WIDTH, FrameLayout.LayoutParams.MATCH_PARENT);
170 | curContentView.addView(mShadowView,1,params);
171 | mShadowView.setX(-SHADOW_WIDTH);
172 | }
173 | }
174 |
175 | //用于防止白屏闪烁
176 | class BackView extends View {
177 |
178 | private View mView;
179 | public BackView(Context context) {
180 | super(context);
181 | }
182 |
183 | public void cacheView(View view){
184 | mView=view;
185 | invalidate();
186 | }
187 |
188 | @Override
189 | protected void onDraw(Canvas canvas) {
190 | super.onDraw(canvas);
191 | if(mView!=null){
192 | mView.draw(canvas);
193 | mView=null;
194 | }
195 | }
196 | }
197 |
198 | class ShadowView extends View{
199 |
200 | private Drawable mDrawable;
201 |
202 | public ShadowView(Context context) {
203 | super(context);
204 | int[] colors=new int[]{0x00000000, 0x17000000, 0x43000000};
205 | mDrawable=new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,colors);
206 | }
207 |
208 | @Override
209 | protected void onDraw(Canvas canvas) {
210 | super.onDraw(canvas);
211 | mDrawable.setBounds(0,0,getMeasuredWidth(),getMeasuredHeight());
212 | mDrawable.draw(canvas);
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Utils/BitmapUtils.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Utils;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.os.Build;
6 | import android.renderscript.Allocation;
7 | import android.renderscript.Element;
8 | import android.renderscript.RenderScript;
9 | import android.renderscript.ScriptIntrinsicBlur;
10 | import android.support.annotation.RequiresApi;
11 | import android.util.Log;
12 |
13 | /**
14 | * Created by mrrobot on 16/11/5.
15 | */
16 |
17 | public class BitmapUtils {
18 | private static final float BITMAP_SCALE = 0.4f;
19 | private static final float BLUR_RADIUS = 7.5f;
20 |
21 | //这种方法对.jpg格式图片支持有问题
22 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
23 | public static Bitmap blur(Context context, Bitmap image) {
24 | int width = Math.round(image.getWidth() * BITMAP_SCALE);
25 | int height = Math.round(image.getHeight() * BITMAP_SCALE);
26 |
27 | Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
28 | Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
29 |
30 | RenderScript rs = RenderScript.create(context);
31 | ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
32 | Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
33 | Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
34 | theIntrinsic.setRadius(BLUR_RADIUS);
35 | theIntrinsic.setInput(tmpIn);
36 | theIntrinsic.forEach(tmpOut);
37 | tmpOut.copyTo(outputBitmap);
38 |
39 | return outputBitmap;
40 | }
41 |
42 | public static Bitmap fastblur(Bitmap sentBitmap, float scale, int radius) {
43 |
44 | int width = Math.round(sentBitmap.getWidth() * scale);
45 | int height = Math.round(sentBitmap.getHeight() * scale);
46 | sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false);
47 |
48 | Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
49 |
50 | if (radius < 1) {
51 | return (null);
52 | }
53 |
54 | int w = bitmap.getWidth();
55 | int h = bitmap.getHeight();
56 |
57 | int[] pix = new int[w * h];
58 | Log.e("pix", w + " " + h + " " + pix.length);
59 | bitmap.getPixels(pix, 0, w, 0, 0, w, h);
60 |
61 | int wm = w - 1;
62 | int hm = h - 1;
63 | int wh = w * h;
64 | int div = radius + radius + 1;
65 |
66 | int r[] = new int[wh];
67 | int g[] = new int[wh];
68 | int b[] = new int[wh];
69 | int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
70 | int vmin[] = new int[Math.max(w, h)];
71 |
72 | int divsum = (div + 1) >> 1;
73 | divsum *= divsum;
74 | int dv[] = new int[256 * divsum];
75 | for (i = 0; i < 256 * divsum; i++) {
76 | dv[i] = (i / divsum);
77 | }
78 |
79 | yw = yi = 0;
80 |
81 | int[][] stack = new int[div][3];
82 | int stackpointer;
83 | int stackstart;
84 | int[] sir;
85 | int rbs;
86 | int r1 = radius + 1;
87 | int routsum, goutsum, boutsum;
88 | int rinsum, ginsum, binsum;
89 |
90 | for (y = 0; y < h; y++) {
91 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
92 | for (i = -radius; i <= radius; i++) {
93 | p = pix[yi + Math.min(wm, Math.max(i, 0))];
94 | sir = stack[i + radius];
95 | sir[0] = (p & 0xff0000) >> 16;
96 | sir[1] = (p & 0x00ff00) >> 8;
97 | sir[2] = (p & 0x0000ff);
98 | rbs = r1 - Math.abs(i);
99 | rsum += sir[0] * rbs;
100 | gsum += sir[1] * rbs;
101 | bsum += sir[2] * rbs;
102 | if (i > 0) {
103 | rinsum += sir[0];
104 | ginsum += sir[1];
105 | binsum += sir[2];
106 | } else {
107 | routsum += sir[0];
108 | goutsum += sir[1];
109 | boutsum += sir[2];
110 | }
111 | }
112 | stackpointer = radius;
113 |
114 | for (x = 0; x < w; x++) {
115 |
116 | r[yi] = dv[rsum];
117 | g[yi] = dv[gsum];
118 | b[yi] = dv[bsum];
119 |
120 | rsum -= routsum;
121 | gsum -= goutsum;
122 | bsum -= boutsum;
123 |
124 | stackstart = stackpointer - radius + div;
125 | sir = stack[stackstart % div];
126 |
127 | routsum -= sir[0];
128 | goutsum -= sir[1];
129 | boutsum -= sir[2];
130 |
131 | if (y == 0) {
132 | vmin[x] = Math.min(x + radius + 1, wm);
133 | }
134 | p = pix[yw + vmin[x]];
135 |
136 | sir[0] = (p & 0xff0000) >> 16;
137 | sir[1] = (p & 0x00ff00) >> 8;
138 | sir[2] = (p & 0x0000ff);
139 |
140 | rinsum += sir[0];
141 | ginsum += sir[1];
142 | binsum += sir[2];
143 |
144 | rsum += rinsum;
145 | gsum += ginsum;
146 | bsum += binsum;
147 |
148 | stackpointer = (stackpointer + 1) % div;
149 | sir = stack[(stackpointer) % div];
150 |
151 | routsum += sir[0];
152 | goutsum += sir[1];
153 | boutsum += sir[2];
154 |
155 | rinsum -= sir[0];
156 | ginsum -= sir[1];
157 | binsum -= sir[2];
158 |
159 | yi++;
160 | }
161 | yw += w;
162 | }
163 | for (x = 0; x < w; x++) {
164 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
165 | yp = -radius * w;
166 | for (i = -radius; i <= radius; i++) {
167 | yi = Math.max(0, yp) + x;
168 |
169 | sir = stack[i + radius];
170 |
171 | sir[0] = r[yi];
172 | sir[1] = g[yi];
173 | sir[2] = b[yi];
174 |
175 | rbs = r1 - Math.abs(i);
176 |
177 | rsum += r[yi] * rbs;
178 | gsum += g[yi] * rbs;
179 | bsum += b[yi] * rbs;
180 |
181 | if (i > 0) {
182 | rinsum += sir[0];
183 | ginsum += sir[1];
184 | binsum += sir[2];
185 | } else {
186 | routsum += sir[0];
187 | goutsum += sir[1];
188 | boutsum += sir[2];
189 | }
190 |
191 | if (i < hm) {
192 | yp += w;
193 | }
194 | }
195 | yi = x;
196 | stackpointer = radius;
197 | for (y = 0; y < h; y++) {
198 | // Preserve alpha channel: ( 0xff000000 & pix[yi] )
199 | pix[yi] = ( 0xff000000 & pix[yi] ) | ( dv[rsum] << 16 ) | ( dv[gsum] << 8 ) | dv[bsum];
200 |
201 | rsum -= routsum;
202 | gsum -= goutsum;
203 | bsum -= boutsum;
204 |
205 | stackstart = stackpointer - radius + div;
206 | sir = stack[stackstart % div];
207 |
208 | routsum -= sir[0];
209 | goutsum -= sir[1];
210 | boutsum -= sir[2];
211 |
212 | if (x == 0) {
213 | vmin[y] = Math.min(y + r1, hm) * w;
214 | }
215 | p = x + vmin[y];
216 |
217 | sir[0] = r[p];
218 | sir[1] = g[p];
219 | sir[2] = b[p];
220 |
221 | rinsum += sir[0];
222 | ginsum += sir[1];
223 | binsum += sir[2];
224 |
225 | rsum += rinsum;
226 | gsum += ginsum;
227 | bsum += binsum;
228 |
229 | stackpointer = (stackpointer + 1) % div;
230 | sir = stack[stackpointer];
231 |
232 | routsum += sir[0];
233 | goutsum += sir[1];
234 | boutsum += sir[2];
235 |
236 | rinsum -= sir[0];
237 | ginsum -= sir[1];
238 | binsum -= sir[2];
239 |
240 | yi += w;
241 | }
242 | }
243 |
244 | Log.e("pix", w + " " + h + " " + pix.length);
245 | bitmap.setPixels(pix, 0, w, 0, 0, w, h);
246 |
247 | return (bitmap);
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Utils;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.widget.Toast;
6 | import java.io.BufferedOutputStream;
7 | import java.io.File;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import me.mrrobot97.designer.retrofit.ApiClient;
11 | import okhttp3.OkHttpClient;
12 | import okhttp3.Request;
13 | import okhttp3.Response;
14 | import rx.Observable;
15 | import rx.Observer;
16 | import rx.android.schedulers.AndroidSchedulers;
17 | import rx.functions.Func0;
18 | import rx.schedulers.Schedulers;
19 |
20 | /**
21 | * Created by mrrobot on 16/11/4.
22 | */
23 |
24 | public class FileUtils {
25 | public static void deleteFileOrDir(String path){
26 | File file=new File(path);
27 | if(file.exists()){
28 | if(file.isFile()){
29 | file.delete();
30 | }else{
31 | for (File f:file.listFiles()){
32 | if(f.isFile()){
33 | f.delete();
34 | }else{
35 | deleteFileOrDir(f.getAbsolutePath());
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
42 | public static boolean isExternalStorageWritable() {
43 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
44 | return true;
45 | }
46 | return false;
47 | }
48 |
49 | public static void saveImage(Context context,String url) {
50 | OkHttpClient client= ApiClient.getClient();
51 | if (!isExternalStorageWritable()) {
52 | Toast.makeText(context, "SD card not available", Toast.LENGTH_SHORT).show();
53 | return;
54 | }
55 | File dir = new File(Environment.getExternalStorageDirectory(), "Designer");
56 | if (!dir.exists()) dir.mkdir();
57 | final File file = new File(dir,
58 | url.substring(url.lastIndexOf(File.separator), url.length()));
59 | Observable.defer(new Func0>() {
60 | @Override public Observable call() {
61 | Request request = new Request.Builder().url(url).build();
62 | Response response = null;
63 | BufferedOutputStream bos = null;
64 | try {
65 | response = client.newCall(request).execute();
66 | bos = new BufferedOutputStream(new FileOutputStream(file));
67 | byte[] buffer = response.body().bytes();
68 | bos.write(buffer, 0, buffer.length);
69 | } catch (IOException e) {
70 | e.printStackTrace();
71 | } finally {
72 | if (bos != null) {
73 | try {
74 | bos.close();
75 | } catch (IOException e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | }
80 | return Observable.just(null);
81 | }
82 | })
83 | .subscribeOn(Schedulers.io())
84 | .observeOn(AndroidSchedulers.mainThread())
85 | .subscribe(new Observer() {
86 |
87 | @Override public void onCompleted() {
88 |
89 | }
90 |
91 | @Override public void onError(Throwable e) {
92 | Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show();
93 | }
94 |
95 | @Override public void onNext(Void aVoid) {
96 | Toast.makeText(context, "Save image to " + file.getAbsolutePath(),
97 | Toast.LENGTH_SHORT).show();
98 | }
99 | });
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Utils/NetUtils.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Utils;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/26.
9 | */
10 |
11 | public class NetUtils {
12 | public static boolean isNetworkOnline(Context context) {
13 | boolean status=false;
14 | try{
15 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
16 | NetworkInfo netInfo = cm.getNetworkInfo(0);
17 | if (netInfo != null && netInfo.getState()==NetworkInfo.State.CONNECTED) {
18 | status= true;
19 | }else {
20 | netInfo = cm.getNetworkInfo(1);
21 | if(netInfo!=null && netInfo.getState()==NetworkInfo.State.CONNECTED)
22 | status= true;
23 | }
24 | }catch(Exception e){
25 | e.printStackTrace();
26 | return false;
27 | }
28 | return status;
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Utils/ScreenUtils.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Utils;
2 |
3 | /**
4 | * Created by mrrobot on 16/10/21.
5 | */
6 |
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.graphics.Bitmap;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 |
14 | /**
15 | * Created by mrrobot on 16/9/13.
16 | */
17 | public class ScreenUtils {
18 |
19 | public static int dp2px(Context context, int dpVal){
20 | return (int) (dpVal*context.getResources().getDisplayMetrics().density);
21 | }
22 |
23 | public static int px2dp(Context context,int pxVal){
24 | return (int) (pxVal/context.getResources().getDisplayMetrics().density);
25 | }
26 |
27 | public static int sp2px(Context cOntext,int spVal){
28 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP
29 | ,spVal,cOntext.getResources().getDisplayMetrics());
30 | }
31 |
32 | public static int px2sp(Context context,int pxVal){
33 | return (int) (pxVal/context.getResources().getDisplayMetrics().scaledDensity);
34 | }
35 |
36 | //return the size of screen in pixel unit
37 | public static int[] getScreenWidthAndHeight(Context context){
38 | int []wh=new int[2];
39 | DisplayMetrics metrics=context.getResources().getDisplayMetrics();
40 | wh[0]=metrics.widthPixels;
41 | wh[1]=metrics.heightPixels;
42 | return wh;
43 | }
44 |
45 | //return the height of statusBar
46 | public static int getStatusBarHeight(Context context){
47 | int statusBarHeight=0;
48 | int resourceId=context.getResources().getIdentifier("status_bar_height","dimen","android");
49 | if (resourceId>0){
50 | statusBarHeight=context.getResources().getDimensionPixelSize(resourceId);
51 | }
52 | return statusBarHeight;
53 | }
54 |
55 | //return a screen capture bitmap
56 | public static Bitmap getScreenCapture(Activity activity){
57 | View view=activity.getWindow().getDecorView();
58 | view.setDrawingCacheEnabled(true);
59 | view.buildDrawingCache();
60 | Bitmap bitmap=view.getDrawingCache();
61 | int []wh=getScreenWidthAndHeight(activity);
62 | Bitmap scaled=Bitmap.createBitmap(bitmap,0,0,wh[0],wh[1]);
63 | view.setDrawingCacheEnabled(false);
64 | view.destroyDrawingCache();
65 | return scaled;
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Utils/SharedPreferencesUtils.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Utils;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import java.util.Set;
6 |
7 | /**
8 | * Created by mrrobot on 16/11/1.
9 | */
10 |
11 | public class SharedPreferencesUtils {
12 | public static String SHARED_FILE_NAME="spfs";
13 |
14 |
15 | //sharedpreferences util
16 | public static void putToSpfs(Context context, String key, Object value){
17 | SharedPreferences.Editor editor=context.getSharedPreferences(SHARED_FILE_NAME,Context.MODE_PRIVATE).edit();
18 | if (value instanceof Integer){
19 | editor.putInt(key, (Integer) value);
20 | }else if(value instanceof String){
21 | editor.putString(key, (String) value);
22 | }else if(value instanceof Boolean){
23 | editor.putBoolean(key, (Boolean) value);
24 | }else if(value instanceof Long){
25 | editor.putLong(key, (Long) value);
26 | }else if(value instanceof Float){
27 | editor.putFloat(key, (Float) value);
28 | }else if(value instanceof Set){
29 | editor.putStringSet(key, (Set) value);
30 | }
31 | editor.apply();
32 | }
33 |
34 | public static Object getFromSpfs(Context context,String key,Object defVal){
35 | SharedPreferences spfs=context.getSharedPreferences(SHARED_FILE_NAME,Context.MODE_PRIVATE);
36 | if (defVal instanceof Integer){
37 | return spfs.getInt(key, (Integer) defVal);
38 | }else if(defVal instanceof String){
39 | return spfs.getString(key, (String) defVal);
40 | }else if(defVal instanceof Boolean){
41 | return spfs.getBoolean(key, (Boolean) defVal);
42 | }else if(defVal instanceof Long){
43 | return spfs.getLong(key, (Long) defVal);
44 | }else if(defVal instanceof Float){
45 | return spfs.getFloat(key, (Float) defVal);
46 | }else if(defVal instanceof Set){
47 | return spfs.getStringSet(key, (Set) defVal);
48 | }
49 | return null;
50 | }
51 |
52 | public static void removeInSpfs(Context context,String key){
53 | SharedPreferences.Editor editor=context.getSharedPreferences(SHARED_FILE_NAME,Context.MODE_PRIVATE).edit();
54 | editor.remove(key);
55 | editor.apply();
56 | }
57 |
58 | public static void clearSpfs(Context context){
59 | SharedPreferences.Editor editor=context.getSharedPreferences(SHARED_FILE_NAME,Context.MODE_PRIVATE).edit();
60 | editor.clear();
61 | editor.apply();
62 | }
63 |
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/Utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.Utils;
2 |
3 | /**
4 | * Created by mrrobot on 16/11/5.
5 | */
6 |
7 | public class StringUtils {
8 | public static boolean checkAvailable(String string){
9 | if(string==null||string.trim().length()==0) return false;
10 | return true;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/adapter/AttachmentsAdapter.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.adapter;
2 |
3 | import android.content.Context;
4 | import android.graphics.Rect;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import butterknife.BindView;
11 | import butterknife.ButterKnife;
12 | import com.bumptech.glide.Glide;
13 | import java.util.List;
14 | import me.mrrobot97.designer.R;
15 | import me.mrrobot97.designer.Utils.ScreenUtils;
16 | import me.mrrobot97.designer.model.Attachment;
17 |
18 | /**
19 | * Created by mrrobot on 16/10/25.
20 | */
21 |
22 | public class AttachmentsAdapter extends RecyclerView.Adapter {
23 | public void setData(List data) {
24 | mData = data;
25 | }
26 |
27 | public static final int ANIM_DURATION = 500;
28 | private List mData;
29 | private int screenWidth;
30 | private Context mContext;
31 | private static final int offset = 8;
32 |
33 | public AttachmentsAdapter(List data, Context context) {
34 | mData = data;
35 | mContext = context;
36 | screenWidth = ScreenUtils.getScreenWidthAndHeight(mContext)[0];
37 | }
38 |
39 | private OnItemClickListener mListener;
40 |
41 | public void setListener(OnItemClickListener listener) {
42 | mListener = listener;
43 | }
44 |
45 | private void runAnim(View view) {
46 | view.setTranslationX(screenWidth);
47 | view.animate().translationX(0).setDuration(ANIM_DURATION).setStartDelay(300).start();
48 | }
49 |
50 | @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
51 | View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_item_layout, null);
52 | //设置宽度
53 | int width = (screenWidth - offset * 4) / 3;
54 | int height = width * 3 / 4;
55 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(width, height);
56 | view.setLayoutParams(params);
57 | MyHolder holder = new MyHolder(view);
58 | runAnim(view);
59 | return holder;
60 | }
61 |
62 | @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
63 | Attachment attachment = mData.get(position);
64 | Glide.with(mContext).load(attachment.getThumbnail_url()).into(((MyHolder) holder).mImageView);
65 | ((MyHolder) holder).mImageView.setOnClickListener(view -> {
66 | if (mListener != null) {
67 | mListener.onItemClicked(position);
68 | }
69 | });
70 | }
71 |
72 | @Override public int getItemCount() {
73 | return mData.size();
74 | }
75 |
76 | public static class MyHolder extends RecyclerView.ViewHolder {
77 | public ImageView getImageView() {
78 | return mImageView;
79 | }
80 |
81 | @BindView(R.id.image_view) ImageView mImageView;
82 |
83 | public MyHolder(View itemView) {
84 | super(itemView);
85 | ButterKnife.bind(this, itemView);
86 | }
87 | }
88 |
89 | public static class MyItemDecoration extends RecyclerView.ItemDecoration {
90 | @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
91 | RecyclerView.State state) {
92 | super.getItemOffsets(outRect, view, parent, state);
93 | outRect.set(offset, offset, offset, offset);
94 | }
95 | }
96 |
97 | public interface OnItemClickListener {
98 | void onItemClicked(int position);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/adapter/ShotsAdapter.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.adapter;
2 |
3 | import android.content.Context;
4 | import android.graphics.Rect;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 |
11 | import com.bumptech.glide.Glide;
12 |
13 | import java.util.List;
14 |
15 | import butterknife.BindView;
16 | import butterknife.ButterKnife;
17 | import me.mrrobot97.designer.R;
18 | import me.mrrobot97.designer.Utils.ScreenUtils;
19 | import me.mrrobot97.designer.model.Shot;
20 |
21 | /**
22 | * Created by mrrobot on 16/10/24.
23 | */
24 | public class ShotsAdapter extends RecyclerView.Adapter {
25 |
26 | public void setData(List data) {
27 | mData = data;
28 | }
29 |
30 | private List mData;
31 | private int screenWidth;
32 | private int screenHeight;
33 | private Context mContext;
34 | private static final int offset = 4;
35 | public static final int ANIM_DURATION = 300;
36 |
37 |
38 | public ShotsAdapter(List data, Context context) {
39 | mData = data;
40 | mContext = context;
41 | screenWidth = ScreenUtils.getScreenWidthAndHeight(mContext)[0];
42 | screenHeight = ScreenUtils.getScreenWidthAndHeight(mContext)[1];
43 | }
44 |
45 | private OnItemClickListener mListener;
46 |
47 | public void setListener(OnItemClickListener listener) {
48 | mListener = listener;
49 | }
50 |
51 | private void runAnimation(View view) {
52 | view.setScaleX(0.69f);
53 | view.setScaleY(0.69f);
54 | view.animate()
55 | .scaleX(1f)
56 | .scaleY(1f)
57 | .setDuration(ANIM_DURATION)
58 | .start();
59 | }
60 |
61 | @Override
62 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
63 | View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_item_layout, null);
64 | //设置宽度
65 | int width = (screenWidth - offset * 4) / 3;
66 | int height = width * 3 / 4;
67 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(width, height);
68 | view.setLayoutParams(params);
69 | MyHolder holder = new MyHolder(view);
70 | return holder;
71 | }
72 |
73 | @Override
74 | public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
75 | runAnimation(holder.itemView);
76 | Shot shot = mData.get(position);
77 | //根据实际屏幕分辨率确定要加载的缩略图的尺寸
78 | String url = null;
79 | if (screenWidth >= 720) {
80 | url = shot.getImages().getNormal();
81 | if (!checkAvailable(url)) url = shot.getImages().getTeaser();
82 | } else {
83 | url = shot.getImages().getTeaser();
84 | }
85 | Glide.with(mContext)
86 | .load(url)
87 | .crossFade()
88 | .into(((MyHolder) holder).mImageView);
89 | ((MyHolder) holder).mImageView.setOnClickListener(view -> {
90 | if (mListener != null) {
91 | mListener.OnItemClicked(position);
92 | }
93 | });
94 | }
95 |
96 | private boolean checkAvailable(String string) {
97 | if (string == null || string.trim().length() == 0) return false;
98 | return true;
99 | }
100 |
101 | @Override
102 | public int getItemCount() {
103 | return mData.size();
104 | }
105 |
106 | public static class MyHolder extends RecyclerView.ViewHolder {
107 | public ImageView getImageView() {
108 | return mImageView;
109 | }
110 |
111 | @BindView(R.id.image_view)
112 | ImageView mImageView;
113 |
114 | public MyHolder(View itemView) {
115 | super(itemView);
116 | ButterKnife.bind(this, itemView);
117 | }
118 | }
119 |
120 | public static class MyItemDecoration extends RecyclerView.ItemDecoration {
121 | @Override
122 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
123 | RecyclerView.State state) {
124 | super.getItemOffsets(outRect, view, parent, state);
125 | outRect.set(offset, offset, offset, offset);
126 | }
127 | }
128 |
129 | public interface OnItemClickListener {
130 | void OnItemClicked(int position);
131 | }
132 | }
133 |
134 |
135 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/contracts/BrowseContract.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.contracts;
2 |
3 | import java.util.List;
4 | import me.mrrobot97.designer.model.Shot;
5 |
6 | /**
7 | * Created by mrrobot on 16/11/7.
8 | */
9 |
10 | public interface BrowseContract {
11 | public interface IBrowseView {
12 | void loadShots(int position,List shots,boolean success);
13 | void refreshShots(int position,List shots,boolean success);
14 | void loadMore(int position,List shots,boolean success);
15 | void requestPermissions();
16 | void showUserProfile();
17 | }
18 |
19 | public interface IBrowsePresenter {
20 | void load(int position);
21 | void refresh(int position);
22 | void loadMore(int position);
23 | }
24 |
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/contracts/DetailContract.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.contracts;
2 |
3 | import java.util.List;
4 | import me.mrrobot97.designer.model.Attachment;
5 | import me.mrrobot97.designer.model.Comment;
6 |
7 | /**
8 | * Created by mrrobot on 16/11/7.
9 | */
10 |
11 | public interface DetailContract {
12 | interface IDetailView {
13 | void showComments(List commments);
14 | void showAttachments(List attachments);
15 | void showIfCommentSuccess(Comment comment,boolean success);
16 | }
17 |
18 | interface IDetailPresenter {
19 | void loadComments(String shotId);
20 |
21 | void loadAttachments(String id);
22 |
23 | void postComment(String id, String comment);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/contracts/PlayerContract.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.contracts;
2 |
3 | import java.util.List;
4 | import me.mrrobot97.designer.model.Shot;
5 | import me.mrrobot97.designer.model.User;
6 |
7 | /**
8 | * Created by mrrobot on 16/11/7.
9 | */
10 |
11 | public interface PlayerContract {
12 | interface IPlayerView {
13 | void showPlayerInfo();
14 | void showPlayerInfoAsync(User user);
15 | void showShots(List shots);
16 | }
17 |
18 | interface IPlayerPresenter {
19 | void loadUserShots(String userId);
20 | void loadUserProfile();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/customViews/CircleImageView.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.customViews;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.BitmapShader;
8 | import android.graphics.Canvas;
9 | import android.graphics.Matrix;
10 | import android.graphics.Paint;
11 | import android.graphics.Shader;
12 | import android.util.AttributeSet;
13 | import android.view.View;
14 | import me.mrrobot97.designer.R;
15 | import me.mrrobot97.designer.Utils.ScreenUtils;
16 |
17 |
18 | /**
19 | * Created by mrrobot on 16/10/21.
20 | */
21 |
22 | public class CircleImageView extends View {
23 |
24 | private Paint mPaint;
25 | private Bitmap bitmap;
26 | private int resId=R.drawable.ic_account;
27 | private int width;
28 | private int height;
29 | private int radius;
30 | private BitmapShader mBitmapShader;
31 | private Matrix matrix;
32 | private int bitmapWidth;
33 | private int bitmapHeigh;
34 | private float scaleSize=1f;
35 |
36 | public CircleImageView(Context context) {
37 | this(context,null);
38 | }
39 |
40 | public CircleImageView(Context context, AttributeSet attrs) {
41 | this(context, attrs,0);
42 | }
43 |
44 | public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
45 | super(context, attrs, defStyleAttr);
46 | TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);
47 | resId=ta.getResourceId(R.styleable.CircleImageView_src,resId);
48 | bitmap= BitmapFactory.decodeResource(getResources(),resId);
49 | ta.recycle();
50 | init();
51 | }
52 |
53 | @Override
54 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
55 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
56 | int widthMode=MeasureSpec.getMode(widthMeasureSpec);
57 | width=MeasureSpec.getSize(widthMeasureSpec);
58 | int heightMode=MeasureSpec.getMode(heightMeasureSpec);
59 | height=MeasureSpec.getSize(heightMeasureSpec);
60 | if(widthMode==MeasureSpec.AT_MOST){
61 | width= ScreenUtils.dp2px(getContext(),50);
62 | }
63 | if(heightMode==MeasureSpec.AT_MOST){
64 | height=ScreenUtils.dp2px(getContext(),50);
65 | }
66 | setMeasuredDimension(width,height);
67 | radius=Math.min(width,height)/2;
68 | setBitmap(bitmap);
69 | }
70 |
71 | private void init() {
72 | mPaint=new Paint();
73 | mPaint.setAntiAlias(true);
74 | matrix=new Matrix();
75 | }
76 |
77 | @Override
78 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
79 | super.onSizeChanged(w, h, oldw, oldh);
80 | width=w;
81 | height=h;
82 | radius=Math.min(width,height)/2;
83 | setBitmap(bitmap);
84 | }
85 |
86 | public void setBitmap(Bitmap bitmap){
87 | matrix.reset();
88 | this.bitmap=bitmap;
89 | mBitmapShader=new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
90 | bitmapWidth=bitmap.getWidth();
91 | bitmapHeigh=bitmap.getHeight();
92 | if(bitmapWidth>width&&bitmapHeigh>height){
93 | scaleSize=Math.min(height*1f/bitmapHeigh,width*1f/bitmapWidth);
94 | }else if(bitmapWidth shots, boolean success);
28 | }
29 |
30 | interface ShotListener {
31 | void onShotLoaded(Shot shot);
32 | }
33 |
34 | interface UserListener {
35 | void onUserLoaded(User user);
36 | }
37 |
38 | interface CommentsListener {
39 | void onCommentsLoader(List comments);
40 | }
41 |
42 | interface UserShotsLoadListener {
43 | void onUserShotsLoaded(List shots);
44 | }
45 |
46 | interface AttachmentsLoadListener {
47 | void onAttachmentsLoaded(List attachments);
48 | }
49 |
50 | interface CommentPostListener {
51 | void onCommentPosted(Comment comment,boolean success);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/model/Images.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/21.
9 | */
10 |
11 | public class Images implements Serializable{
12 | @SerializedName("hidpi")
13 | private String hidpi;
14 | @SerializedName("normal")
15 | private String normal;
16 | @SerializedName("teaser")
17 | private String teaser;
18 |
19 | public Images(String hidpi, String normal, String teaser) {
20 | this.hidpi = hidpi;
21 | this.normal = normal;
22 | this.teaser = teaser;
23 | }
24 |
25 | public String getHidpi() {
26 |
27 | return hidpi;
28 | }
29 |
30 | public void setHidpi(String hidpi) {
31 | this.hidpi = hidpi;
32 | }
33 |
34 | public String getNormal() {
35 | return normal;
36 | }
37 |
38 | public void setNormal(String normal) {
39 | this.normal = normal;
40 | }
41 |
42 | public String getTeaser() {
43 | return teaser;
44 | }
45 |
46 | public void setTeaser(String teaser) {
47 | this.teaser = teaser;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/model/Links.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/24.
9 | */
10 |
11 | public class Links implements Serializable{
12 | @SerializedName("web")
13 | private String web;
14 | @SerializedName("twitter")
15 | private String twitter;
16 |
17 | public String getWeb() {
18 | return web;
19 | }
20 |
21 | public void setWeb(String web) {
22 | this.web = web;
23 | }
24 |
25 | public String getTwitter() {
26 | return twitter;
27 | }
28 |
29 | public void setTwitter(String twitter) {
30 | this.twitter = twitter;
31 | }
32 |
33 | public Links(String web, String twitter) {
34 |
35 | this.web = web;
36 | this.twitter = twitter;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/model/ModelImpl.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.model;
2 |
3 | import android.util.Log;
4 | import java.util.List;
5 | import me.mrrobot97.designer.retrofit.ApiClient;
6 | import me.mrrobot97.designer.retrofit.DribbbleService;
7 | import rx.Subscriber;
8 | import rx.android.schedulers.AndroidSchedulers;
9 | import rx.schedulers.Schedulers;
10 |
11 | /**
12 | * Created by mrrobot on 16/10/21.
13 | */
14 |
15 | public class ModelImpl implements IModel {
16 | private DribbbleService observable;
17 |
18 | public ModelImpl() {
19 | observable= ApiClient.getRetrofit().create(DribbbleService.class);
20 | }
21 |
22 | @Override
23 | public void loadShots(String sort, int page, int per_page, final ShotsListener listener) {
24 | if(sort.equals("debuts")){
25 | observable.loadDebutsShots(sort,page,per_page)
26 | .subscribeOn(Schedulers.newThread())
27 | .observeOn(AndroidSchedulers.mainThread())
28 | .subscribe(new Subscriber>() {
29 | @Override
30 | public void onCompleted() {
31 |
32 | }
33 |
34 | @Override
35 | public void onError(Throwable e) {
36 | Log.d("yjw","onError");
37 | listener.onShotsLoaded(null,false);
38 | }
39 |
40 | @Override
41 | public void onNext(List shots) {
42 | listener.onShotsLoaded(shots,true);
43 | }
44 | });
45 | }else{
46 | observable.loadShots(sort,page,per_page)
47 | .subscribeOn(Schedulers.newThread())
48 | .observeOn(AndroidSchedulers.mainThread())
49 | .subscribe(new Subscriber>() {
50 | @Override
51 | public void onCompleted() {
52 |
53 | }
54 |
55 | @Override
56 | public void onError(Throwable e) {
57 | Log.d("yjw","onError");
58 | listener.onShotsLoaded(null,false);
59 | }
60 |
61 | @Override
62 | public void onNext(List shots) {
63 | listener.onShotsLoaded(shots,true);
64 | }
65 | });
66 | }
67 |
68 | }
69 |
70 | @Override
71 | public void loadShotDetail(String id, final ShotListener listener) {
72 | observable.loadShot(id)
73 | .subscribeOn(Schedulers.newThread())
74 | .observeOn(AndroidSchedulers.mainThread())
75 | .subscribe(new Subscriber() {
76 | @Override
77 | public void onCompleted() {
78 | Log.d("yjw","onCompleted");
79 | }
80 |
81 | @Override
82 | public void onError(Throwable e) {
83 | Log.d("yjw","onError");
84 | }
85 |
86 | @Override
87 | public void onNext(Shot shot) {
88 | listener.onShotLoaded(shot);
89 | }
90 | });
91 |
92 | }
93 |
94 | @Override
95 | public void loadUser(String id, final UserListener listener) {
96 | observable.loadUser(id)
97 | .subscribeOn(Schedulers.newThread())
98 | .observeOn(AndroidSchedulers.mainThread())
99 | .subscribe(new Subscriber() {
100 | @Override
101 | public void onCompleted() {
102 | Log.d("yjw","onCompleted");
103 | }
104 |
105 | @Override
106 | public void onError(Throwable e) {
107 | Log.d("yjw","onError");
108 | }
109 |
110 | @Override
111 | public void onNext(User user) {
112 | listener.onUserLoaded(user);
113 | }
114 | });
115 | }
116 |
117 | @Override
118 | public void loadComments(String shotId, final CommentsListener listener) {
119 | observable.loadComments(shotId)
120 | .subscribeOn(Schedulers.newThread())
121 | .observeOn(AndroidSchedulers.mainThread())
122 | .subscribe(new Subscriber>() {
123 | @Override
124 | public void onCompleted() {
125 |
126 | }
127 |
128 | @Override
129 | public void onError(Throwable e) {
130 | Log.d("yjw","comments loading error");
131 | }
132 |
133 | @Override
134 | public void onNext(List comments) {
135 | listener.onCommentsLoader(comments);
136 | }
137 | });
138 | }
139 |
140 | @Override
141 | public void loadUserShots(String userId, final UserShotsLoadListener listener) {
142 | observable.loadUserShots(userId)
143 | .subscribeOn(Schedulers.newThread())
144 | .observeOn(AndroidSchedulers.mainThread())
145 | .subscribe(new Subscriber>() {
146 | @Override
147 | public void onCompleted() {
148 |
149 | }
150 |
151 | @Override
152 | public void onError(Throwable e) {
153 | Log.d("yjw","shots loading error");
154 | }
155 |
156 | @Override
157 | public void onNext(List shots) {
158 | listener.onUserShotsLoaded(shots);
159 | }
160 | });
161 | }
162 |
163 | @Override
164 | public void loadAttachments(String id, final AttachmentsLoadListener listener) {
165 | observable.loadAttachments(id)
166 | .subscribeOn(Schedulers.newThread())
167 | .observeOn(AndroidSchedulers.mainThread())
168 | .subscribe(new Subscriber>() {
169 | @Override
170 | public void onCompleted() {
171 |
172 | }
173 |
174 | @Override
175 | public void onError(Throwable e) {
176 | Log.d("yjw","shots loading error");
177 | }
178 |
179 | @Override
180 | public void onNext(List attachments) {
181 | listener.onAttachmentsLoaded(attachments);
182 | }
183 | });
184 | }
185 |
186 | @Override public void loadUserProfile( final UserListener listener) {
187 | observable.loadUserProfile()
188 | .subscribeOn(Schedulers.newThread())
189 | .observeOn(AndroidSchedulers.mainThread())
190 | .subscribe(new Subscriber() {
191 | @Override public void onCompleted() {
192 |
193 | }
194 |
195 | @Override public void onError(Throwable e) {
196 | Log.d("yjw","user profile load error");
197 | }
198 |
199 | @Override public void onNext(User user) {
200 | listener.onUserLoaded(user);
201 | }
202 | });
203 | }
204 |
205 | @Override
206 | public void postComment(String id, String comment, CommentPostListener listener) {
207 | observable.postComment(id,comment)
208 | .subscribeOn(Schedulers.newThread())
209 | .observeOn(AndroidSchedulers.mainThread())
210 | .subscribe(new Subscriber() {
211 | @Override public void onCompleted() {
212 |
213 | }
214 |
215 | @Override public void onError(Throwable e) {
216 | Log.d("yjw","comment failed");
217 | listener.onCommentPosted(null,false);
218 | }
219 |
220 | @Override public void onNext(Comment comment) {
221 | listener.onCommentPosted(comment,true);
222 | }
223 | });
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/model/Shot.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/21.
9 | */
10 |
11 | public class Shot implements Serializable{
12 | public Shot(String id, String title, String description, int width, String height, Images images, int comments_count
13 | , int views_count, int likes_count, int attachments_count, String created_at, String html_url, String attachments_url
14 | , String comments_url, boolean animated, String[] tags, User user) {
15 | this.id = id;
16 | this.title = title;
17 | this.description = description;
18 | this.width = width;
19 | this.height = height;
20 | this.images = images;
21 | this.comments_count = comments_count;
22 | this.views_count = views_count;
23 | this.likes_count = likes_count;
24 | this.attachments_count = attachments_count;
25 | this.created_at = created_at;
26 | this.html_url = html_url;
27 | this.attachments_url = attachments_url;
28 | this.comments_url = comments_url;
29 | this.animated = animated;
30 | this.tags = tags;
31 | this.user = user;
32 | }
33 |
34 | public String getId() {
35 | return id;
36 | }
37 |
38 | public void setId(String id) {
39 | this.id = id;
40 | }
41 |
42 | public String getTitle() {
43 | return title;
44 | }
45 |
46 | public void setTitle(String title) {
47 | this.title = title;
48 | }
49 |
50 | public String getDescription() {
51 | return description;
52 | }
53 |
54 | public void setDescription(String description) {
55 | this.description = description;
56 | }
57 |
58 | public int getWidth() {
59 | return width;
60 | }
61 |
62 | public void setWidth(int width) {
63 | this.width = width;
64 | }
65 |
66 | public String getHeight() {
67 | return height;
68 | }
69 |
70 | public void setHeight(String height) {
71 | this.height = height;
72 | }
73 |
74 | public Images getImages() {
75 | return images;
76 | }
77 |
78 | public void setImages(Images images) {
79 | this.images = images;
80 | }
81 |
82 | public int getViews_count() {
83 | return views_count;
84 | }
85 |
86 | public void setViews_count(int views_count) {
87 | this.views_count = views_count;
88 | }
89 |
90 | public int getLikes_count() {
91 | return likes_count;
92 | }
93 |
94 | public void setLikes_count(int likes_count) {
95 | this.likes_count = likes_count;
96 | }
97 |
98 | public int getAttachments_count() {
99 | return attachments_count;
100 | }
101 |
102 | public void setAttachments_count(int attachments_count) {
103 | this.attachments_count = attachments_count;
104 | }
105 |
106 | public String getCreated_at() {
107 | return created_at;
108 | }
109 |
110 | public void setCreated_at(String created_at) {
111 | this.created_at = created_at;
112 | }
113 |
114 | public String getHtml_url() {
115 | return html_url;
116 | }
117 |
118 | public void setHtml_url(String html_url) {
119 | this.html_url = html_url;
120 | }
121 |
122 | public String getAttachments_url() {
123 | return attachments_url;
124 | }
125 |
126 | public void setAttachments_url(String attachments_url) {
127 | this.attachments_url = attachments_url;
128 | }
129 |
130 | public String getComments_url() {
131 | return comments_url;
132 | }
133 |
134 | public void setComments_url(String comments_url) {
135 | this.comments_url = comments_url;
136 | }
137 |
138 | public boolean isAnimated() {
139 | return animated;
140 | }
141 |
142 | public void setAnimated(boolean animated) {
143 | this.animated = animated;
144 | }
145 |
146 | public String[] getTags() {
147 | return tags;
148 | }
149 |
150 | public void setTags(String[] tags) {
151 | this.tags = tags;
152 | }
153 |
154 | public User getUser() {
155 | return user;
156 | }
157 |
158 | public void setUser(User user) {
159 | this.user = user;
160 | }
161 |
162 | public int getComments_count() {
163 | return comments_count;
164 | }
165 |
166 | public void setComments_count(int comments_count) {
167 | this.comments_count = comments_count;
168 | }
169 |
170 | @SerializedName("id")
171 | private String id;
172 | @SerializedName("title")
173 | private String title;
174 | @SerializedName("description")
175 | private String description;
176 | @SerializedName("width")
177 | private int width;
178 | @SerializedName("height")
179 | private String height;
180 | @SerializedName("images")
181 | private Images images;
182 | @SerializedName("comments_count")
183 | private int comments_count;
184 | @SerializedName("views_count")
185 | private int views_count;
186 | @SerializedName("likes_count")
187 | private int likes_count;
188 | @SerializedName("attachments_count")
189 | private int attachments_count;
190 | @SerializedName("created_at")
191 | private String created_at;
192 | @SerializedName("html_url")
193 | private String html_url;
194 | @SerializedName("attachments_url")
195 | private String attachments_url;
196 | @SerializedName("comments_url")
197 | private String comments_url;
198 | @SerializedName("animated")
199 | private boolean animated;
200 | @SerializedName("tags")
201 | private String[] tags;
202 | @SerializedName("user")
203 | private User user;
204 | }
205 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/model/User.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.model;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/21.
9 | */
10 |
11 | public class User implements Serializable{
12 | @SerializedName("id")
13 | private String id;
14 | @SerializedName("name")
15 | private String name;
16 | @SerializedName("username")
17 | private String username;
18 | @SerializedName("html_url")
19 | private String html_url;
20 | @SerializedName("avatar_url")
21 | private String avatar_url;
22 | @SerializedName("buckets_count")
23 | private int buckets_count;
24 | @SerializedName("comments_received_count")
25 | private int comments_received_count;
26 | @SerializedName("followers_count")
27 | private int followers_count;
28 | @SerializedName("followings_count")
29 | private int followings_count;
30 | @SerializedName("projects_count")
31 | private int projects_count;
32 | @SerializedName("shots_count")
33 | private int shots_count;
34 | @SerializedName("type")
35 | private String type;
36 | @SerializedName("buckets_url")
37 | private String buckets_url;
38 | @SerializedName("followers_url")
39 | private String followers_url;
40 | @SerializedName("following_url")
41 | private String following_url;
42 | @SerializedName("projects_url")
43 | private String projects_url;
44 | @SerializedName("shots_url")
45 | private String shots_url;
46 | @SerializedName("created_at")
47 | private String created_at;
48 | @SerializedName("location")
49 | private String location;
50 | @SerializedName("links")
51 | private Links links;
52 |
53 | public User(String id, String name, String username, String html_url, String avatar_url, int buckets_count, int comments_received_count,
54 | int followers_count, int followings_count, int projects_count, int shots_count, String type, String buckets_url,
55 | String followers_url, String following_url, String projects_url, String shots_url, String created_at, String location,
56 | Links links) {
57 | this.id = id;
58 | this.name = name;
59 | this.username = username;
60 | this.html_url = html_url;
61 | this.avatar_url = avatar_url;
62 | this.buckets_count = buckets_count;
63 | this.comments_received_count = comments_received_count;
64 | this.followers_count = followers_count;
65 | this.followings_count = followings_count;
66 | this.projects_count = projects_count;
67 | this.shots_count = shots_count;
68 | this.type = type;
69 | this.buckets_url = buckets_url;
70 | this.followers_url = followers_url;
71 | this.following_url = following_url;
72 | this.projects_url = projects_url;
73 | this.shots_url = shots_url;
74 | this.created_at = created_at;
75 | this.location = location;
76 | this.links = links;
77 | }
78 |
79 | public String getLocation() {
80 | return location;
81 | }
82 |
83 | public void setLocation(String location) {
84 | this.location = location;
85 | }
86 |
87 |
88 | public String getId() {
89 | return id;
90 | }
91 |
92 | public void setId(String id) {
93 | this.id = id;
94 | }
95 |
96 | public String getName() {
97 | return name;
98 | }
99 |
100 | public void setName(String name) {
101 | this.name = name;
102 | }
103 |
104 | public String getUsername() {
105 | return username;
106 | }
107 |
108 | public void setUsername(String username) {
109 | this.username = username;
110 | }
111 |
112 | public String getHtml_url() {
113 | return html_url;
114 | }
115 |
116 | public void setHtml_url(String html_url) {
117 | this.html_url = html_url;
118 | }
119 |
120 | public String getAvatar_url() {
121 | return avatar_url;
122 | }
123 |
124 | public void setAvatar_url(String avatar_url) {
125 | this.avatar_url = avatar_url;
126 | }
127 |
128 | public int getBuckets_count() {
129 | return buckets_count;
130 | }
131 |
132 | public void setBuckets_count(int buckets_count) {
133 | this.buckets_count = buckets_count;
134 | }
135 |
136 | public int getComments_received_count() {
137 | return comments_received_count;
138 | }
139 |
140 | public void setComments_received_count(int comments_received_count) {
141 | this.comments_received_count = comments_received_count;
142 | }
143 |
144 | public int getFollowers_count() {
145 | return followers_count;
146 | }
147 |
148 | public void setFollowers_count(int follwers_count) {
149 | this.followers_count = follwers_count;
150 | }
151 |
152 | public int getFollowings_count() {
153 | return followings_count;
154 | }
155 |
156 | public void setFollwings_count(int follwings_count) {
157 | this.followings_count = follwings_count;
158 | }
159 |
160 | public int getProjects_count() {
161 | return projects_count;
162 | }
163 |
164 | public void setProjects_count(int projects_count) {
165 | this.projects_count = projects_count;
166 | }
167 |
168 | public int getShots_count() {
169 | return shots_count;
170 | }
171 |
172 | public void setShots_count(int shots_count) {
173 | this.shots_count = shots_count;
174 | }
175 |
176 | public String getType() {
177 | return type;
178 | }
179 |
180 | public void setType(String type) {
181 | this.type = type;
182 | }
183 |
184 | public String getBuckets_url() {
185 | return buckets_url;
186 | }
187 |
188 | public void setBuckets_url(String buckets_url) {
189 | this.buckets_url = buckets_url;
190 | }
191 |
192 | public String getFollowers_url() {
193 | return followers_url;
194 | }
195 |
196 | public void setFollowers_url(String followers_url) {
197 | this.followers_url = followers_url;
198 | }
199 |
200 | public String getFollowing_url() {
201 | return following_url;
202 | }
203 |
204 | public void setFollowing_url(String following_url) {
205 | this.following_url = following_url;
206 | }
207 |
208 | public String getProjects_url() {
209 | return projects_url;
210 | }
211 |
212 | public void setProjects_url(String projects_url) {
213 | this.projects_url = projects_url;
214 | }
215 |
216 | public String getShots_url() {
217 | return shots_url;
218 | }
219 |
220 | public void setShots_url(String shots_url) {
221 | this.shots_url = shots_url;
222 | }
223 |
224 | public String getCreated_at() {
225 | return created_at;
226 | }
227 |
228 | public void setCreated_at(String created_at) {
229 | this.created_at = created_at;
230 | }
231 |
232 | public Links getLinks() {
233 | return links;
234 | }
235 |
236 | public void setLinks(Links links) {
237 | this.links = links;
238 | }
239 |
240 | }
241 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/presenter/BrowsePresenter.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.presenter;
2 |
3 | import java.util.List;
4 | import me.mrrobot97.designer.contracts.BrowseContract;
5 | import me.mrrobot97.designer.model.IModel;
6 | import me.mrrobot97.designer.model.ModelImpl;
7 | import me.mrrobot97.designer.model.Shot;
8 |
9 | /**
10 | * Created by mrrobot on 16/10/21.
11 | */
12 |
13 | public class BrowsePresenter implements BrowseContract.IBrowsePresenter {
14 |
15 | private BrowseContract.IBrowseView mView;
16 | private IModel mModel;
17 | private int[] pages=new int[]{1,1,1};
18 | private final int PER_PAGE=30;
19 | private String[] sorts=new String[]{"popular","debuts","recent"};
20 |
21 | public BrowsePresenter(BrowseContract.IBrowseView view) {
22 | mView = view;
23 | mModel=new ModelImpl();
24 | }
25 |
26 |
27 | @Override
28 | public void load(final int position) {
29 | pages[position]=1;
30 | mModel.loadShots(sorts[position], pages[position], PER_PAGE, (List shots,boolean success)-> {
31 | mView.loadShots(position,shots,success);
32 | if(success) pages[position]++;
33 | });
34 | }
35 |
36 | @Override
37 | public void refresh(final int position) {
38 | pages[position]=1;
39 | mModel.loadShots(sorts[position], pages[position], PER_PAGE, (List shots,boolean success) -> {
40 | mView.refreshShots(position,shots,success);
41 | if(success)pages[position]++;
42 | });
43 | }
44 |
45 | @Override
46 | public void loadMore(final int position) {
47 | mModel.loadShots(sorts[position], pages[position], PER_PAGE, (List shots,boolean success) -> {
48 | mView.loadMore(position,shots,success);
49 | if(success)pages[position]++;
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/presenter/DetailPresenter.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.presenter;
2 |
3 | import me.mrrobot97.designer.contracts.DetailContract;
4 | import me.mrrobot97.designer.model.IModel;
5 | import me.mrrobot97.designer.model.ModelImpl;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/23.
9 | */
10 |
11 | public class DetailPresenter implements DetailContract.IDetailPresenter {
12 | private DetailContract.IDetailView mView;
13 | private IModel mModel;
14 |
15 | public DetailPresenter(DetailContract.IDetailView view) {
16 | mView = view;
17 | mModel=new ModelImpl();
18 | }
19 |
20 | @Override
21 | public void loadComments(String shotId) {
22 | mModel.loadComments(shotId, comments -> mView.showComments(comments));
23 | }
24 |
25 | @Override
26 | public void loadAttachments(String id) {
27 | mModel.loadAttachments(id, attachments -> mView.showAttachments(attachments));
28 | }
29 |
30 | @Override public void postComment(String id, String comment) {
31 | mModel.postComment(id,comment,(comment1,success) -> mView.showIfCommentSuccess(comment1,success));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/presenter/PlayerPresenter.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.presenter;
2 |
3 | import me.mrrobot97.designer.contracts.PlayerContract;
4 | import me.mrrobot97.designer.model.IModel;
5 | import me.mrrobot97.designer.model.ModelImpl;
6 |
7 | /**
8 | * Created by mrrobot on 16/10/24.
9 | */
10 |
11 | public class PlayerPresenter implements PlayerContract.IPlayerPresenter {
12 | private PlayerContract.IPlayerView mView;
13 | private IModel mModel;
14 |
15 | public PlayerPresenter(PlayerContract.IPlayerView view) {
16 | mView = view;
17 | mModel=new ModelImpl();
18 | }
19 |
20 | @Override
21 | public void loadUserShots(String userId) {
22 | mModel.loadUserShots(userId, shots -> mView.showShots(shots));
23 | }
24 |
25 | @Override public void loadUserProfile() {
26 | mModel.loadUserProfile( user -> mView.showPlayerInfoAsync(user));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/retrofit/ApiClient.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.retrofit;
2 |
3 | import java.io.File;
4 | import me.mrrobot97.designer.MyApplication;
5 | import me.mrrobot97.designer.Utils.NetUtils;
6 | import me.mrrobot97.designer.Utils.SharedPreferencesUtils;
7 | import okhttp3.Cache;
8 | import okhttp3.CacheControl;
9 | import okhttp3.Interceptor;
10 | import okhttp3.OkHttpClient;
11 | import okhttp3.Request;
12 | import okhttp3.Response;
13 | import retrofit2.Retrofit;
14 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
15 | import retrofit2.converter.gson.GsonConverterFactory;
16 |
17 | /**
18 | * Created by mrrobot on 16/10/21.
19 | */
20 |
21 | public class ApiClient {
22 | public static final String CACHE_DIR="CacheFile";
23 | public static final String baseUrl = "https://api.dribbble.com/v1/";
24 | public static final String access_token =
25 | "a62b88ea291c0d0e5b9295fdb8930936f945027bb84ff747ef6b89f8a9cd4da1";
26 | public static String user_access_token;
27 |
28 | private static Retrofit retrofit;
29 | private static OkHttpClient client;
30 |
31 | public static OkHttpClient getClient(){
32 | return client;
33 | }
34 |
35 | public static Retrofit getRetrofit() {
36 | user_access_token=
37 | (String) SharedPreferencesUtils.getFromSpfs(MyApplication.getContext(),"access_token",null);
38 | String token=user_access_token;
39 | if(token==null) token=access_token;
40 | if (retrofit == null) {
41 | synchronized (Retrofit.class) {
42 | if (retrofit == null) {
43 | String finalToken = token;
44 | Interceptor interceptor = chain -> {
45 | Request original = chain.request();
46 | Request request = null;
47 | if (NetUtils.isNetworkOnline(MyApplication.getContext())) {
48 | request = original.newBuilder()
49 | .addHeader("Authorization", "Bearer " + finalToken)
50 | .build();
51 | } else {
52 | request = original.newBuilder()
53 | .addHeader("Authorization", "Bearer " + finalToken)
54 | .cacheControl(CacheControl.FORCE_CACHE)
55 | .build();
56 | }
57 | Response response = chain.proceed(request);
58 | if (NetUtils.isNetworkOnline(MyApplication.getContext())) {
59 | int maxAge = 60 * 60 * 1; // read from cache for 1 hour
60 | return response.newBuilder()
61 | .removeHeader("Pragma")
62 | .removeHeader("Cache-Control")
63 | .header("Cache-Control", "public, max-age=" + maxAge)
64 | .build();
65 | } else {
66 | int maxStale = 60 * 60 * 24 * 7; // tolerate one week
67 | return response.newBuilder()
68 | .removeHeader("Pragma")
69 | .removeHeader("Cache-Control")
70 | .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
71 | .build();
72 | }
73 | };
74 | //设置OKHttpClient的缓存目录
75 | File cacheDir = MyApplication.getContext().getCacheDir();
76 | File cacheFile = new File(cacheDir, CACHE_DIR);
77 |
78 | client =
79 | new OkHttpClient.Builder()
80 | .cache(new Cache(cacheFile, 1024 * 1024 * 50))
81 | .addNetworkInterceptor(interceptor)
82 | .build();
83 |
84 | retrofit = new Retrofit.Builder().baseUrl(baseUrl)
85 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
86 | .addConverterFactory(GsonConverterFactory.create())
87 | .client(client)
88 | .build();
89 | }
90 | }
91 | }
92 | return retrofit;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/retrofit/DribbbleService.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.retrofit;
2 |
3 | import java.util.List;
4 | import me.mrrobot97.designer.model.Attachment;
5 | import me.mrrobot97.designer.model.Comment;
6 | import me.mrrobot97.designer.model.Shot;
7 | import me.mrrobot97.designer.model.User;
8 | import retrofit2.http.Field;
9 | import retrofit2.http.FormUrlEncoded;
10 | import retrofit2.http.GET;
11 | import retrofit2.http.POST;
12 | import retrofit2.http.Path;
13 | import retrofit2.http.Query;
14 | import rx.Observable;
15 |
16 | /**
17 | * Created by mrrobot on 16/10/21.
18 | */
19 |
20 | public interface DribbbleService {
21 | @GET("users/{id}")
22 | Observable loadUser(@Path("id") String id);
23 |
24 | @GET("shots")
25 | Observable> loadShots(@Query("sort")String sort,@Query("page")int page,@Query("per_page")int per_page);
26 |
27 | @GET("shots")
28 | Observable> loadDebutsShots(@Query("list")String list,@Query("page")int page,@Query("per_page")int per_page);
29 |
30 | @GET("shots/{id}")
31 | Observable loadShot(@Path("id")String id);
32 |
33 | @GET("shots/{id}/comments")
34 | Observable> loadComments(@Path("id")String id);
35 |
36 | @GET("users/{id}/shots")
37 | Observable> loadUserShots(@Path("id")String userId);
38 |
39 | @GET("shots/{id}/attachments")
40 | Observable> loadAttachments(@Path("id")String id);
41 |
42 | @GET("user")
43 | Observable loadUserProfile();
44 |
45 | //账号没有comment权限,无法测试功能是否正常
46 | @FormUrlEncoded
47 | @POST("shots/{id}/comments")
48 | Observable postComment(@Path("id")String id,@Field("body") String comment);
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/view/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.view;
2 |
3 | import android.content.Intent;
4 | import android.os.Build;
5 | import android.os.Bundle;
6 | import android.support.annotation.LayoutRes;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.app.ActivityCompat;
9 | import android.support.v4.app.ActivityOptionsCompat;
10 | import android.support.v4.app.Fragment;
11 | import android.support.v7.widget.GridLayoutManager;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.TextView;
17 | import butterknife.BindView;
18 | import butterknife.ButterKnife;
19 | import java.util.List;
20 | import me.mrrobot97.designer.R;
21 | import me.mrrobot97.designer.adapter.ShotsAdapter;
22 | import me.mrrobot97.designer.model.Shot;
23 |
24 | /**
25 | * Created by mrrobot on 16/10/21.
26 | */
27 |
28 | public class BaseFragment extends Fragment {
29 |
30 | @BindView(R.id.recycler_view)RecyclerView mRecyclerView;
31 | @BindView(R.id.view_loading)TextView loadingView;
32 |
33 | private static final String LAYOUT_ID="layout_id";
34 | private int layoutId;
35 | private ShotsAdapter mAdapter;
36 | private List mData;
37 |
38 | public void setLoading(boolean loading) {
39 | isLoading = loading;
40 | }
41 |
42 | private boolean isLoading=false;
43 |
44 | public interface SlideToBottomListener{
45 | void whenSlideToBottom();
46 | }
47 | private SlideToBottomListener mListener;
48 |
49 | public void setListener(SlideToBottomListener listener) {
50 | mListener = listener;
51 | }
52 |
53 | @Nullable
54 | @Override
55 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
56 | layoutId=getArguments().getInt(LAYOUT_ID);
57 | View view=inflater.inflate(layoutId,container,false);
58 | ButterKnife.bind(this,view);
59 | mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(),3));
60 | mRecyclerView.addItemDecoration(new ShotsAdapter.MyItemDecoration());
61 | mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
62 | @Override
63 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
64 | if(isSlideToBottom()){
65 | if(mListener!=null&&!isLoading){
66 | showLoadingView();
67 | mListener.whenSlideToBottom();
68 | }
69 | }
70 | }
71 | });
72 | return view;
73 | }
74 |
75 | public void setData(List shots){
76 | if(mData!=null){
77 | throw new RuntimeException("You can call setData() only once!");
78 | }
79 | mData=shots;
80 | mAdapter=new ShotsAdapter(mData,getContext());
81 | mAdapter.setListener(position -> {
82 | Intent intent=new Intent(getContext(),DetailActivity.class);
83 | intent.putExtra("shot",mData.get(position));
84 | if(Build.VERSION.SDK_INT shots){
99 | mData=shots;
100 | mAdapter.notifyDataSetChanged();
101 | loadingView.setVisibility(View.GONE);
102 | isLoading=false;
103 | }
104 |
105 | public void loadMoreData(List shots){
106 | if(mData==null){
107 | throw new RuntimeException("You must call setData() before refreshData()!");
108 | }
109 | for(Shot shot:shots){
110 | mData.add(shot);
111 | }
112 | mAdapter.notifyDataSetChanged();
113 | loadingView.setVisibility(View.GONE);
114 | isLoading=false;
115 | }
116 |
117 | public void showLoadingView(){
118 | if(loadingView!=null){
119 | loadingView.setVisibility(View.VISIBLE);
120 | isLoading=true;
121 | }
122 | }
123 |
124 | public void cancleLoadingView(){
125 | loadingView.setVisibility(View.GONE);
126 | }
127 |
128 | private boolean isSlideToBottom(){
129 | if(mRecyclerView==null) return false;
130 | if(mRecyclerView.computeVerticalScrollExtent()+mRecyclerView.computeVerticalScrollOffset()
131 | >=mRecyclerView.computeVerticalScrollRange())
132 | return true;
133 | return false;
134 | }
135 |
136 | public static BaseFragment newInstance(@LayoutRes int layout_id){
137 | Bundle bundle=new Bundle();
138 | bundle.putInt(LAYOUT_ID,layout_id);
139 | BaseFragment fragment=new BaseFragment();
140 | fragment.setArguments(bundle);
141 | return fragment;
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/view/BrowseActivity.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.view;
2 |
3 | import android.Manifest;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.os.Bundle;
7 | import android.support.annotation.NonNull;
8 | import android.support.design.widget.Snackbar;
9 | import android.support.design.widget.TabLayout;
10 | import android.support.v4.app.ActivityCompat;
11 | import android.support.v4.app.Fragment;
12 | import android.support.v4.app.FragmentPagerAdapter;
13 | import android.support.v4.view.ViewPager;
14 | import android.support.v4.widget.SwipeRefreshLayout;
15 | import android.support.v7.app.AppCompatActivity;
16 | import android.support.v7.widget.Toolbar;
17 | import android.view.Menu;
18 | import android.view.MenuItem;
19 | import android.widget.RelativeLayout;
20 | import android.widget.Toast;
21 | import butterknife.BindView;
22 | import butterknife.ButterKnife;
23 | import com.bumptech.glide.Glide;
24 | import java.io.File;
25 | import java.util.List;
26 | import me.mrrobot97.designer.R;
27 | import me.mrrobot97.designer.Utils.FileUtils;
28 | import me.mrrobot97.designer.Utils.NetUtils;
29 | import me.mrrobot97.designer.Utils.ScreenUtils;
30 | import me.mrrobot97.designer.Utils.SharedPreferencesUtils;
31 | import me.mrrobot97.designer.contracts.BrowseContract;
32 | import me.mrrobot97.designer.model.Shot;
33 | import me.mrrobot97.designer.presenter.BrowsePresenter;
34 | import me.mrrobot97.designer.retrofit.ApiClient;
35 |
36 | public class BrowseActivity extends AppCompatActivity implements BrowseContract.IBrowseView {
37 | @BindView(R.id.activity_browse) RelativeLayout mContainer;
38 | @BindView(R.id.tool_bar) Toolbar mToolbar;
39 | @BindView(R.id.swipe_refresh_layout) SwipeRefreshLayout mRefreshLayout;
40 | @BindView(R.id.tab_layout) TabLayout mTabLayout;
41 | @BindView(R.id.view_pager) ViewPager mViewPager;
42 |
43 | public static final int ANIM_DURATION=500;
44 | private Snackbar mSnackBar;
45 | private FragmentPagerAdapter mAdapter;
46 | private String[] mTitles = new String[] { "Popular", "Debuts", "Recent" };
47 | private static final int REQUEST_CODE = 0;
48 | private boolean isSnackBarShown = false;
49 | private boolean isLogin = false;
50 | private Runnable runnable = ()->{
51 | if (mRefreshLayout != null) {
52 | mRefreshLayout.setRefreshing(false);
53 | }};
54 |
55 | private BaseFragment[] mFragments = new BaseFragment[3];
56 |
57 | BrowseContract.IBrowsePresenter mPresenter;
58 | private MenuItem userProfileItem;
59 |
60 | @Override protected void onCreate(Bundle savedInstanceState) {
61 | super.onCreate(savedInstanceState);
62 | setContentView(R.layout.activity_browse);
63 | ButterKnife.bind(this);
64 | setSupportActionBar(mToolbar);
65 | getSupportActionBar().setDisplayShowTitleEnabled(false);
66 | //getSupportActionBar().setDisplayShowHomeEnabled(true);
67 | //mToolbar.setNavigationIcon(R.drawable.ic_menu);
68 | mToolbar.setOnMenuItemClickListener(item -> {
69 | switch (item.getItemId()) {
70 | case R.id.log_out:
71 | SharedPreferencesUtils.putToSpfs(getApplicationContext(), "login", false);
72 | Intent intent = new Intent(BrowseActivity.this, LoginActivity.class);
73 | startActivity(intent);
74 | finish();
75 | break;
76 | case R.id.user:
77 | showUserProfile();
78 | break;
79 | case R.id.clear_cache:
80 | clearUserCache();
81 | default:
82 | break;
83 | }
84 | return true;
85 | });
86 | mPresenter = new BrowsePresenter(this);
87 | if (!ifPermissionGranted()) {
88 | requestPermissions();
89 | } else {
90 | init();
91 | }
92 | }
93 |
94 | private void startToolbarAnimation() {
95 | int toolbarHeight=ScreenUtils.dp2px(this,56);
96 | mToolbar.setTranslationY(-toolbarHeight);
97 | userProfileItem.getActionView().setTranslationY(-toolbarHeight);
98 | mToolbar.animate()
99 | .translationY(0)
100 | .setDuration(ANIM_DURATION)
101 | .setStartDelay(300).start();
102 | userProfileItem.getActionView().animate()
103 | .translationY(0)
104 | .setDuration(ANIM_DURATION)
105 | .setStartDelay(500).start();
106 | }
107 |
108 | private void clearUserCache() {
109 | new Thread(()->{
110 | //clear OKHttp cache
111 | FileUtils.deleteFileOrDir(getCacheDir().getAbsolutePath()+ File.separator+ ApiClient.CACHE_DIR);
112 | //clear Glide cache
113 | Glide.get(BrowseActivity.this).clearDiskCache();
114 | //Glide.get(BrowseActivity.this).clearMemory();
115 | Toast.makeText(BrowseActivity.this, "Clear Cache success", Toast.LENGTH_SHORT).show();
116 | }).start();
117 | }
118 |
119 | private void init() {
120 | isLogin = (boolean) SharedPreferencesUtils.getFromSpfs(getApplicationContext(), "login", false);
121 | mSnackBar = Snackbar.make(mContainer, "Sure to exit?", Snackbar.LENGTH_INDEFINITE)
122 | .setAction("Sure", view -> finish());
123 | mFragments[0] = BaseFragment.newInstance(R.layout.fragment_layout);
124 | mFragments[1] = BaseFragment.newInstance(R.layout.fragment_layout);
125 | mFragments[2] = BaseFragment.newInstance(R.layout.fragment_layout);
126 | mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
127 | @Override public Fragment getItem(int position) {
128 | return mFragments[position];
129 | }
130 |
131 | @Override public int getCount() {
132 | return mFragments.length;
133 | }
134 |
135 | @Override public CharSequence getPageTitle(int position) {
136 | return mTitles[position];
137 | }
138 | };
139 | for (int i = 0; i < mTitles.length; i++) {
140 | mTabLayout.addTab(mTabLayout.newTab());
141 | }
142 | //设置左右各自缓存的fragment数量,默认为1,若保持默认,则当fragment切换至3时,1的fragment会调用onDestroyView,界面就销毁了。
143 | mViewPager.setOffscreenPageLimit(2);
144 | mViewPager.setAdapter(mAdapter);
145 | mTabLayout.setTabMode(TabLayout.MODE_FIXED);
146 | mTabLayout.setupWithViewPager(mViewPager);
147 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
148 | @Override
149 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
150 |
151 | }
152 |
153 | @Override public void onPageSelected(int position) {
154 |
155 | }
156 |
157 | @Override public void onPageScrollStateChanged(int state) {
158 | //控制只有当viewpager为静止状态时才能触发SwipeRefreshLayout
159 | mRefreshLayout.setEnabled(state == ViewPager.SCROLL_STATE_IDLE);
160 | }
161 | });
162 | mRefreshLayout.setOnRefreshListener(()->{
163 | mPresenter.refresh(mViewPager.getCurrentItem());
164 | mRefreshLayout.setRefreshing(true);
165 | });
166 | //设置下拉刷新的出发高度为屏幕高度的1/3
167 | mRefreshLayout.setDistanceToTriggerSync(
168 | ScreenUtils.getScreenWidthAndHeight(getApplicationContext())[1] / 3);
169 | mRefreshLayout.post(()->mRefreshLayout.setRefreshing(true));
170 | loadData();
171 | if (!NetUtils.isNetworkOnline(this)) {
172 | Toast.makeText(this, "客官,您没有联网呦~", Toast.LENGTH_SHORT).show();
173 | }
174 | }
175 |
176 | private void loadData() {
177 | for (int i = 0; i < mFragments.length; i++) {
178 | mPresenter.load(i);
179 | mFragments[i].showLoadingView();
180 | final int finalI = i;
181 | mFragments[i].setListener(()->mPresenter.loadMore(finalI));
182 | }
183 | }
184 |
185 | @Override public void loadShots(int position, List shots, boolean success) {
186 | mRefreshLayout.post(runnable);
187 | if (!success) {
188 | mFragments[position].setLoading(false);
189 | Toast.makeText(this, "客官,数据加载出错了呦~", Toast.LENGTH_SHORT).show();
190 | } else {
191 | mFragments[position].setData(shots);
192 | }
193 | }
194 |
195 | @Override public void refreshShots(int position, List shots, boolean success) {
196 | mRefreshLayout.post(runnable);
197 | if (!success) {
198 | mFragments[position].setLoading(false);
199 | Toast.makeText(this, "客官,数据加载出错了呦~", Toast.LENGTH_SHORT).show();
200 | return;
201 | } else {
202 | mFragments[position].refreshDate(shots);
203 | }
204 | }
205 |
206 | @Override public void loadMore(int position, List shots, boolean success) {
207 | mRefreshLayout.post(runnable);
208 | mFragments[position].cancleLoadingView();
209 | if (!success) {
210 | mFragments[position].setLoading(false);
211 | Toast.makeText(this, "客官,加载出错了呦~", Toast.LENGTH_SHORT).show();
212 | return;
213 | } else {
214 | mFragments[position].loadMoreData(shots);
215 | mRefreshLayout.setRefreshing(false);
216 | }
217 | }
218 |
219 | public boolean ifPermissionGranted() {
220 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
221 | != PackageManager.PERMISSION_GRANTED) {
222 | return false;
223 | }
224 | return true;
225 | }
226 |
227 | @Override public void requestPermissions() {
228 | ActivityCompat.requestPermissions(this,
229 | new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_CODE);
230 | }
231 |
232 | @Override public void showUserProfile() {
233 | if (isLogin) {
234 | Intent intent = new Intent(BrowseActivity.this, PlayerActivity.class);
235 | //start DetailActivity without parsing a User
236 | startActivity(intent);
237 | } else {
238 | Intent intent = new Intent(this, LoginActivity.class);
239 | startActivity(intent);
240 | }
241 | }
242 |
243 | @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
244 | @NonNull int[] grantResults) {
245 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
246 | if (requestCode == REQUEST_CODE) {
247 | if (grantResults.length > 0) {
248 | finish();
249 | } else {
250 | init();
251 | }
252 | }
253 | }
254 |
255 | @Override public void onBackPressed() {
256 | if (isSnackBarShown) {
257 | mSnackBar.dismiss();
258 | isSnackBarShown = false;
259 | } else {
260 | mSnackBar.show();
261 | isSnackBarShown = true;
262 | }
263 | }
264 |
265 | @Override public boolean onCreateOptionsMenu(Menu menu) {
266 | getMenuInflater().inflate(R.menu.menu_browse, menu);
267 | MenuItem item = menu.getItem(1);
268 | userProfileItem=menu.findItem(R.id.user);
269 | userProfileItem.setActionView(R.layout.user_menu_item_view);
270 | userProfileItem.getActionView().setOnClickListener(view -> showUserProfile());
271 | if (isLogin) {
272 | item.setTitle("Log out");
273 | } else {
274 | item.setTitle("Sign in");
275 | }
276 | startToolbarAnimation();
277 | return true;
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/view/DetailActivity.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.graphics.Bitmap;
8 | import android.graphics.drawable.BitmapDrawable;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.support.v4.app.ActivityCompat;
12 | import android.support.v4.app.ActivityOptionsCompat;
13 | import android.support.v7.app.AlertDialog;
14 | import android.support.v7.graphics.Palette;
15 | import android.support.v7.widget.LinearLayoutManager;
16 | import android.support.v7.widget.RecyclerView;
17 | import android.support.v7.widget.Toolbar;
18 | import android.text.Html;
19 | import android.text.Spannable;
20 | import android.view.LayoutInflater;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.view.ViewTreeObserver;
24 | import android.view.animation.AccelerateInterpolator;
25 | import android.view.animation.DecelerateInterpolator;
26 | import android.widget.Button;
27 | import android.widget.EditText;
28 | import android.widget.ImageView;
29 | import android.widget.RelativeLayout;
30 | import android.widget.TextView;
31 | import android.widget.Toast;
32 | import butterknife.BindView;
33 | import butterknife.ButterKnife;
34 | import com.bumptech.glide.Glide;
35 | import com.bumptech.glide.request.animation.GlideAnimation;
36 | import com.bumptech.glide.request.target.SimpleTarget;
37 | import java.util.List;
38 | import me.mrrobot97.designer.R;
39 | import me.mrrobot97.designer.SwipeActivity.SwipeBackActivity;
40 | import me.mrrobot97.designer.Utils.BitmapUtils;
41 | import me.mrrobot97.designer.Utils.FileUtils;
42 | import me.mrrobot97.designer.Utils.ScreenUtils;
43 | import me.mrrobot97.designer.Utils.SharedPreferencesUtils;
44 | import me.mrrobot97.designer.adapter.AttachmentsAdapter;
45 | import me.mrrobot97.designer.contracts.DetailContract;
46 | import me.mrrobot97.designer.customViews.CircleImageView;
47 | import me.mrrobot97.designer.customViews.HoverView;
48 | import me.mrrobot97.designer.model.Attachment;
49 | import me.mrrobot97.designer.model.Comment;
50 | import me.mrrobot97.designer.model.Shot;
51 | import me.mrrobot97.designer.presenter.DetailPresenter;
52 | import me.mrrobot97.designer.retrofit.ApiClient;
53 | import okhttp3.OkHttpClient;
54 |
55 | import static me.mrrobot97.designer.Utils.StringUtils.checkAvailable;
56 |
57 | //// TODO: 16/11/4 DetailActivity界面加载图片太慢,待解决
58 | public class DetailActivity extends SwipeBackActivity implements DetailContract.IDetailView {
59 | @BindView(R.id.avatar) CircleImageView avatar;
60 | @BindView(R.id.shot_title) TextView shotTitle;
61 | @BindView(R.id.author) TextView author;
62 | @BindView(R.id.image_view) ImageView mImageView;
63 | @BindView(R.id.tool_bar) Toolbar mToolbar;
64 | @BindView(R.id.view_cnt) TextView viewCnt;
65 | @BindView(R.id.comments_cnt) TextView commentsCnt;
66 | @BindView(R.id.likes_cnt) TextView likesCnt;
67 | @BindView(R.id.hover_view) HoverView mHoverView;
68 | @BindView(R.id.front_image_view) ImageView frontImageView;
69 | @BindView(R.id.comments_recycler_view) RecyclerView mCommentsRecycler;
70 | @BindView(R.id.share_iv) ImageView mShareIv;
71 | @BindView(R.id.recyclerview) RecyclerView attachmentRecyclerview;
72 | @BindView(R.id.attachment_layout) RelativeLayout attachmentLayout;
73 | @BindView(R.id.profile_layout)RelativeLayout profileLayout;
74 | @BindView(R.id.edit_comment)EditText mEditComment;
75 | @BindView(R.id.bt_send)Button mBtSend;
76 |
77 | public static final int ANIM_DURATION=500;
78 |
79 | private Shot mShot;
80 | private int screenWidth;
81 | private ValueAnimator mAnimator;
82 | private AlertDialog.Builder mBuilder;
83 | private AlertDialog dialog;
84 | private OkHttpClient client;
85 | private String url;
86 | private List mComments;
87 | private DetailContract.IDetailPresenter mPresenter;
88 | private MyAdapter mAdapter = new MyAdapter();
89 | private AttachmentsAdapter attachAdapter;
90 | private String frontImageUrl;
91 | private boolean isLogin=false;
92 | private String token=null;
93 |
94 |
95 | @Override protected void onCreate(Bundle savedInstanceState) {
96 | super.onCreate(savedInstanceState);
97 | setContentView(R.layout.activity_detail);
98 | ButterKnife.bind(this);
99 | setSupportActionBar(mToolbar);
100 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
101 | getSupportActionBar().setDisplayShowTitleEnabled(false);
102 | mToolbar.setNavigationOnClickListener(view -> finish());
103 | screenWidth = ScreenUtils.getScreenWidthAndHeight(getApplicationContext())[0];
104 | isLogin= (boolean) SharedPreferencesUtils.getFromSpfs(this,"login",false);
105 | token= (String) SharedPreferencesUtils.getFromSpfs(this,"access_token",null);
106 | init();
107 | }
108 |
109 | @Override protected void onNewIntent(Intent intent) {
110 | super.onNewIntent(intent);
111 | init();
112 | }
113 |
114 | private void prepareAnimator(){
115 | mAnimator = ValueAnimator.ofFloat(1f, 0f);
116 | mAnimator.setDuration(200);
117 | mAnimator.setInterpolator(new DecelerateInterpolator());
118 | mAnimator.addUpdateListener(valueAnimator -> {
119 | float alpha = (float) valueAnimator.getAnimatedValue();
120 | mHoverView.setAlpha(alpha);
121 | frontImageView.setAlpha(alpha);
122 | });
123 | mAnimator.addListener(new Animator.AnimatorListener() {
124 | @Override public void onAnimationStart(Animator animator) {
125 |
126 | }
127 |
128 | @Override public void onAnimationEnd(Animator animator) {
129 | mHoverView.setVisibility(View.GONE);
130 | frontImageView.setVisibility(View.GONE);
131 | }
132 |
133 | @Override public void onAnimationCancel(Animator animator) {
134 |
135 | }
136 |
137 | @Override public void onAnimationRepeat(Animator animator) {
138 |
139 | }
140 | });
141 | }
142 |
143 | private void init() {
144 | mShot = (Shot) getIntent().getSerializableExtra("shot");
145 | mPresenter = new DetailPresenter(this);
146 | client = ApiClient.getClient();
147 | mCommentsRecycler.setLayoutManager(new LinearLayoutManager(this));
148 | attachmentRecyclerview.setLayoutManager(
149 | new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
150 | mImageView.setMinimumHeight(screenWidth * 3 / 4);
151 | url = mShot.getImages().getHidpi();
152 | if (!checkAvailable(url)) {
153 | url = mShot.getImages().getNormal();
154 | }if (!checkAvailable(url)) {
155 | url = mShot.getImages().getTeaser();
156 | }
157 | mBuilder = new AlertDialog.Builder(this);
158 | mBuilder.setItems(new String[] { "保存图片" }, (DialogInterface dialogInterface, int i) -> {
159 | FileUtils.saveImage(this,frontImageUrl);
160 | });
161 | dialog = mBuilder.create();
162 | setListener();
163 | prepareAnimator();
164 | setTextInfo();
165 | changeToolBarColor();
166 | generateBlurBitmap();
167 | loadData();
168 | }
169 |
170 | private void setListener() {
171 | mImageView.setOnClickListener(view -> {
172 | frontImageUrl = url;
173 | showFullScreenView();
174 | });
175 | frontImageView.setOnClickListener(view -> mAnimator.start());
176 | mHoverView.setOnClickListener(view -> mAnimator.start());
177 | frontImageView.setOnLongClickListener(view -> {
178 | dialog.show();
179 | return true;
180 | });
181 | avatar.setOnClickListener(view -> {
182 | Intent intent = new Intent(DetailActivity.this, PlayerActivity.class);
183 | intent.putExtra("author", mShot.getUser());
184 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
185 | startActivity(intent);
186 | } else {
187 | //5.0及以上系统实现共享元素动画
188 | ActivityOptionsCompat options =
189 | ActivityOptionsCompat.makeSceneTransitionAnimation(DetailActivity.this, avatar,
190 | getString(R.string.transitionAvatar));
191 | ActivityCompat.startActivity(DetailActivity.this, intent, options.toBundle());
192 | }
193 | });
194 | if(!isLogin){
195 | mEditComment.setEnabled(false);
196 | }
197 | mBtSend.setOnClickListener(view -> {
198 | if(!isLogin){
199 | Toast.makeText(this, "Please sign in.", Toast.LENGTH_SHORT).show();
200 | return;
201 | }
202 | String comment=mEditComment.getText().toString();
203 | if(comment==null||comment.trim().length()==0) {
204 | Toast.makeText(this, "Empty comment.", Toast.LENGTH_SHORT).show();
205 | return;
206 | }
207 | mEditComment.setText("");
208 | mPresenter.postComment(mShot.getId(),comment);
209 | });
210 | }
211 |
212 | private void setTextInfo() {
213 | shotTitle.setText(mShot.getTitle());
214 | author.setText(mShot.getUser().getUsername());
215 | viewCnt.setText(mShot.getViews_count() + "");
216 | commentsCnt.setText(mShot.getComments_count() + "");
217 | likesCnt.setText(mShot.getLikes_count() + "");
218 | mShareIv.setOnClickListener(view -> shareShot());
219 | }
220 |
221 | private void loadData() {
222 | //load avatar
223 | SimpleTarget target = new SimpleTarget() {
224 | @Override
225 | public void onResourceReady(Bitmap resource, GlideAnimation super Bitmap> glideAnimation) {
226 | avatar.setBitmap(resource);
227 | }
228 | };
229 | Glide.with(this).load(mShot.getUser().getAvatar_url()).asBitmap().into(target);
230 | //load attachments
231 | if (mShot.getAttachments_count() > 0) {
232 | mPresenter.loadAttachments(mShot.getId());
233 | }
234 | //load comments
235 | mPresenter.loadComments(mShot.getId());
236 | }
237 |
238 | private void changeToolBarColor() {
239 | //使用Palette动态改变Toolbar背景色
240 | SimpleTarget targetPalette=new SimpleTarget() {
241 | public void onResourceReady(Bitmap resource, GlideAnimation super Bitmap> glideAnimation) {
242 | Palette.from(resource).generate(palette -> {
243 | int vibrantColor=palette.getVibrantColor(getResources().getColor(R.color.colorPrimaryDark));
244 | mToolbar.post(()->{
245 | mToolbar.setBackgroundColor(vibrantColor);
246 | profileLayout.setBackgroundColor(vibrantColor);
247 | if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)
248 | getWindow().setStatusBarColor(vibrantColor);
249 | });
250 | });
251 | }
252 | };
253 | Glide.with(this).load(mShot.getImages().getTeaser()).asBitmap().into(targetPalette);
254 | }
255 |
256 | private void generateBlurBitmap() {
257 | String blurUrl=null;
258 | //复用在BrowseAcrtivity中下载的缩略图
259 | if (screenWidth >= 720) {
260 | blurUrl=mShot.getImages().getNormal();
261 | if(!checkAvailable(url)) url=mShot.getImages().getTeaser();
262 | } else {
263 | blurUrl=mShot.getImages().getTeaser();
264 | }
265 | SimpleTarget target=new SimpleTarget() {
266 | @Override
267 | public void onResourceReady(Bitmap resource, GlideAnimation super Bitmap> glideAnimation) {
268 | Bitmap blurImage= BitmapUtils.fastblur(resource,0.4f,8);
269 | BitmapDrawable drawable=new BitmapDrawable(blurImage);
270 | Glide.with(DetailActivity.this).load(url).placeholder(drawable).crossFade().into(mImageView);
271 | Glide.with(DetailActivity.this).load(url).placeholder(drawable).crossFade().into(frontImageView);
272 | }
273 | };
274 | Glide.with(DetailActivity.this).load(blurUrl).asBitmap().into(target);
275 | }
276 |
277 | void showFullScreenView() {
278 | mHoverView.setVisibility(View.VISIBLE);
279 | frontImageView.setVisibility(View.VISIBLE);
280 | mHoverView.setAlpha(1f);
281 | frontImageView.setAlpha(1f);
282 | Glide.with(DetailActivity.this).load(frontImageUrl).crossFade().into(frontImageView);
283 | }
284 |
285 | private void shareShot() {
286 | Intent intent = new Intent(Intent.ACTION_SEND);
287 | intent.setType("text/plain");
288 |
289 | intent.putExtra(Intent.EXTRA_SUBJECT, mShot.getTitle());
290 | intent.putExtra(Intent.EXTRA_TEXT,
291 | "I found an amazing design in dribbble: " + mShot.getHtml_url());
292 | startActivity(Intent.createChooser(intent, "Share design"));
293 | }
294 |
295 |
296 | @Override public void showComments(List commments) {
297 | mComments = commments;
298 | mAdapter = new MyAdapter();
299 | mCommentsRecycler.setAdapter(mAdapter);
300 | mCommentsRecycler.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
301 | @Override public boolean onPreDraw() {
302 | mCommentsRecycler.getViewTreeObserver().removeOnPreDrawListener(this);
303 | mCommentsRecycler.setScaleY(0.1f);
304 | mCommentsRecycler.animate()
305 | .scaleY(1f)
306 | .setDuration(ANIM_DURATION)
307 | .setInterpolator(new AccelerateInterpolator())
308 | .start();
309 | return true;
310 | }
311 | });
312 | }
313 |
314 | @Override public void showAttachments(final List attachments) {
315 | attachmentLayout.setVisibility(View.VISIBLE);
316 | attachmentRecyclerview.setVisibility(View.VISIBLE);
317 | attachAdapter = new AttachmentsAdapter(attachments, DetailActivity.this);
318 | //attachAdapter.setListener(position -> {
319 | // Attachment attachment = attachments.get(position);
320 | // frontImageUrl = attachment.getUrl();
321 | // showFullScreenView();
322 | //});
323 | attachAdapter.setListener(position -> {
324 | Attachment attachment = attachments.get(position);
325 | frontImageUrl = attachment.getUrl();
326 | showFullScreenView();
327 | });
328 | attachmentRecyclerview.setAdapter(attachAdapter);
329 | }
330 |
331 | @Override public void showIfCommentSuccess(Comment comment,boolean success) {
332 | if (success){
333 | mComments.add(comment);
334 | mAdapter.notifyItemInserted(mComments.size()-1);
335 | }else{
336 | Toast.makeText(this, "Comment failed,Please make that you have permission to comment.", Toast.LENGTH_SHORT).show();
337 | }
338 | }
339 |
340 | class MyAdapter extends RecyclerView.Adapter {
341 |
342 | @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
343 | View view = LayoutInflater.from(DetailActivity.this)
344 | .inflate(R.layout.comment_item_layout, parent, false);
345 | MyHolder holder = new MyHolder(view);
346 | return holder;
347 | }
348 |
349 | @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
350 | Comment comment = mComments.get(position);
351 | SimpleTarget target = new SimpleTarget() {
352 | @Override public void onResourceReady(Bitmap resource,
353 | GlideAnimation super Bitmap> glideAnimation) {
354 | ((MyHolder) holder).mAvatar.setBitmap(resource);
355 | }
356 | };
357 | Glide.with(DetailActivity.this)
358 | .load(comment.getUser().getAvatar_url())
359 | .asBitmap()
360 | .into(target);
361 | ((MyHolder) holder).mAuthor.setText(comment.getUser().getUsername());
362 | Spannable sp = (Spannable) Html.fromHtml(comment.getBody());
363 | ((MyHolder) holder).mContent.setText(sp.toString().trim());
364 | if (position % 2 != 0) {
365 | ((MyHolder) holder).mRelativeLayout.setBackgroundColor(getColor(R.color.textColorWhite));
366 | }
367 | }
368 |
369 | @Override public int getItemCount() {
370 | return mComments.size();
371 | }
372 |
373 | class MyHolder extends RecyclerView.ViewHolder {
374 | @BindView(R.id.comment_background) RelativeLayout mRelativeLayout;
375 | @BindView(R.id.comment_avatar) CircleImageView mAvatar;
376 | @BindView(R.id.comment_content) TextView mContent;
377 | @BindView(R.id.comment_author) TextView mAuthor;
378 |
379 | public MyHolder(View itemView) {
380 | super(itemView);
381 | ButterKnife.bind(this, itemView);
382 | }
383 | }
384 | }
385 | }
386 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/view/LoginActivity.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.view;
2 |
3 | import android.content.Intent;
4 | import android.graphics.Typeface;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.View;
8 | import android.webkit.WebChromeClient;
9 | import android.webkit.WebSettings;
10 | import android.webkit.WebView;
11 | import android.webkit.WebViewClient;
12 | import android.widget.Button;
13 | import android.widget.ProgressBar;
14 | import android.widget.RelativeLayout;
15 | import android.widget.TextView;
16 | import android.widget.Toast;
17 | import butterknife.BindView;
18 | import butterknife.ButterKnife;
19 | import java.io.IOException;
20 | import java.util.HashMap;
21 | import java.util.Map;
22 | import me.mrrobot97.designer.R;
23 | import me.mrrobot97.designer.Utils.FileUtils;
24 | import me.mrrobot97.designer.Utils.SharedPreferencesUtils;
25 | import okhttp3.Call;
26 | import okhttp3.Callback;
27 | import okhttp3.FormBody;
28 | import okhttp3.OkHttpClient;
29 | import okhttp3.Request;
30 | import okhttp3.RequestBody;
31 | import okhttp3.Response;
32 | import org.json.JSONException;
33 | import org.json.JSONObject;
34 |
35 | //// TODO: 16/11/1 WebView缓存问题,每次login前手动删除缓存文件,打开APP第一次认证正常,但第二次登录还是会直接跳过,尚未找到原因。
36 | public class LoginActivity extends AppCompatActivity {
37 | @BindView(R.id.bt_login)Button mBtLogin;
38 | @BindView(R.id.bt_non_login)Button mBtNon;
39 | @BindView(R.id.txt_logo)TextView mTxtLogo;
40 | @BindView(R.id.web_view)WebView mWebView;
41 | @BindView(R.id.loading_layout)RelativeLayout loadingLayout;
42 | @BindView(R.id.back_layout)RelativeLayout backLayout;
43 | @BindView(R.id.progressbar)ProgressBar mProgressBar;
44 | @BindView(R.id.webview_layout)RelativeLayout mWebviewLayout;
45 |
46 | private final String AUTH_URL="https://dribbble.com/oauth/authorize";
47 | private final String TOKEN_URL="https://dribbble.com/oauth/token";
48 | private final String CLIENT_ID="a655a6b9c5440762a30d30f85142afc6fe34ff3419ee5864130034f613b1791c";
49 | private final String CLIENT_SECRET="9c5de21b644560851a8823fb47de3c3ce223d9d3f67a27a8c73346ff02caa98a";
50 | private final String CACHE_DIR="/data/data/me.mrrobot97.designer/app_webview";
51 | private final String CACHE_FILE="/data/data/me.mrrobot97.designer/cache/org.chromium.android_webview";
52 | private final String SCOPE="public+write+comment";
53 | private String code;
54 | private String token;
55 | private OkHttpClient client;
56 |
57 |
58 | @Override protected void onCreate(Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | setContentView(R.layout.activity_login);
61 | ButterKnife.bind(this);
62 | boolean isLogin=
63 | (boolean) SharedPreferencesUtils.getFromSpfs(getApplicationContext(),"login",false);
64 | if(isLogin){
65 | startBrowseActivity();
66 | }
67 | init();
68 | }
69 |
70 | private void startBrowseActivity(){
71 | Intent intent=new Intent(LoginActivity.this,BrowseActivity.class);
72 | startActivity(intent);
73 | finish();
74 | }
75 |
76 | private void init() {
77 | Typeface typeface=Typeface.createFromAsset(getAssets(),"fonts/New Walt Disney.ttf");
78 | mTxtLogo.setTypeface(typeface);
79 | client=new OkHttpClient();
80 | mBtLogin.setOnClickListener(view -> login());
81 | mBtNon.setOnClickListener(view -> nonLogin());
82 | WebSettings settings=mWebView.getSettings();
83 | settings.setAppCacheEnabled(false);
84 | settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
85 | settings.setJavaScriptEnabled(true);
86 | settings.setDomStorageEnabled(false);
87 | mWebView.setWebViewClient(new WebViewClient(){
88 | @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
89 | if(url.contains("code")){
90 | //重定向网页,参数中包含了code
91 | code=url.substring(url.lastIndexOf("=")+1);
92 | mWebviewLayout.setVisibility(View.GONE);
93 | getToken();
94 | return false;
95 | }
96 | view.loadUrl(url);
97 | return true;
98 | }
99 | });
100 | mWebView.setWebChromeClient(new WebChromeClient(){
101 | @Override public void onProgressChanged(WebView view, int newProgress) {
102 | super.onProgressChanged(view, newProgress);
103 | mProgressBar.setProgress(newProgress);
104 | }
105 | });
106 | }
107 |
108 | private void nonLogin() {
109 | SharedPreferencesUtils.putToSpfs(getApplicationContext(),"login",false);
110 | startBrowseActivity();
111 | }
112 |
113 | private void login() {
114 | //两个缓存目录下的文件都要手动删除,否则重新登录的时候会因为缓存而直接进入
115 | //目前每次从新打开APP第一次登录正常,之后的全部直接走缓存了。
116 | FileUtils.deleteFileOrDir(CACHE_DIR);
117 | FileUtils.deleteFileOrDir(CACHE_FILE);
118 | mWebView.clearCache(true);
119 | Map header=new HashMap<>();
120 | header.put("Cache-Control","no-cache");
121 | mWebviewLayout.setVisibility(View.VISIBLE);
122 | backLayout.setVisibility(View.INVISIBLE);
123 | mWebView.loadUrl(AUTH_URL+"?client_id="+CLIENT_ID+"&scope="+SCOPE,header);
124 | }
125 |
126 | public void getToken() {
127 | backLayout.setVisibility(View.VISIBLE);
128 | loadingLayout.setVisibility(View.VISIBLE);
129 | RequestBody postParam= new FormBody.Builder().add("client_id",CLIENT_ID).add("client_secret",CLIENT_SECRET).add("code",code).build();
130 | final Request request=new Request.Builder().url(TOKEN_URL).post(postParam).build();
131 | client.newCall(request).enqueue(new Callback() {
132 | @Override public void onFailure(Call call, IOException e) {
133 | loadingLayout.setVisibility(View.GONE);
134 | backLayout.setVisibility(View.VISIBLE);
135 | Toast.makeText(LoginActivity.this, "获取认证信息失败,请重新登录", Toast.LENGTH_SHORT).show();
136 | }
137 |
138 | @Override public void onResponse(Call call, Response response) throws IOException {
139 | try {
140 | loadingLayout.post(()->{
141 | loadingLayout.setVisibility(View.GONE);
142 | backLayout.setVisibility(View.VISIBLE);
143 | });
144 | JSONObject object=new JSONObject(response.body().string());
145 | token=object.getString("access_token");
146 | SharedPreferencesUtils.putToSpfs(getApplicationContext(),"access_token",token);
147 | SharedPreferencesUtils.putToSpfs(getApplicationContext(),"login",true);
148 | startBrowseActivity();
149 | } catch (JSONException e) {
150 | e.printStackTrace();
151 | }
152 | }
153 | });
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/app/src/main/java/me/mrrobot97/designer/view/PlayerActivity.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.graphics.Bitmap;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.support.annotation.RequiresApi;
12 | import android.support.v7.app.AlertDialog;
13 | import android.support.v7.widget.LinearLayoutManager;
14 | import android.support.v7.widget.RecyclerView;
15 | import android.support.v7.widget.Toolbar;
16 | import android.view.View;
17 | import android.view.animation.DecelerateInterpolator;
18 | import android.widget.ImageView;
19 | import android.widget.RelativeLayout;
20 | import android.widget.TextView;
21 | import butterknife.BindView;
22 | import butterknife.ButterKnife;
23 | import com.bumptech.glide.Glide;
24 | import com.bumptech.glide.request.animation.GlideAnimation;
25 | import com.bumptech.glide.request.target.SimpleTarget;
26 | import java.util.List;
27 | import me.mrrobot97.designer.R;
28 | import me.mrrobot97.designer.SwipeActivity.SwipeBackActivity;
29 | import me.mrrobot97.designer.Utils.BitmapUtils;
30 | import me.mrrobot97.designer.Utils.FileUtils;
31 | import me.mrrobot97.designer.Utils.ScreenUtils;
32 | import me.mrrobot97.designer.Utils.StringUtils;
33 | import me.mrrobot97.designer.adapter.ShotsAdapter;
34 | import me.mrrobot97.designer.contracts.PlayerContract;
35 | import me.mrrobot97.designer.customViews.HoverView;
36 | import me.mrrobot97.designer.model.Shot;
37 | import me.mrrobot97.designer.model.User;
38 | import me.mrrobot97.designer.presenter.PlayerPresenter;
39 |
40 | //// TODO: 16/11/5 用户名与所在地址显示位置有问题,待解决
41 | public class PlayerActivity extends SwipeBackActivity implements PlayerContract.IPlayerView, View.OnClickListener {
42 | @BindView(R.id.tool_bar) Toolbar mToolbar;
43 | @BindView(R.id.avatar) ImageView avatar;
44 | @BindView(R.id.name) TextView name;
45 | @BindView(R.id.location) TextView location;
46 | @BindView(R.id.follower_num) TextView followers;
47 | @BindView(R.id.following_num) TextView followings;
48 | @BindView(R.id.recyclerview) RecyclerView mRecyclerView;
49 | @BindView(R.id.twitter_url) TextView twitterUrl;
50 | @BindView(R.id.website_url) TextView websiteUrl;
51 | @BindView(R.id.dribbble_url) TextView dribbbleUrl;
52 | @BindView(R.id.twitter_link) ImageView twitterLink;
53 | @BindView(R.id.browser_link) ImageView browserLink;
54 | @BindView(R.id.dribbble_link) ImageView dribbbleLink;
55 | @BindView(R.id.hover_view) HoverView mHoverView;
56 | @BindView(R.id.front_image_view) ImageView frontImageView;
57 | @BindView(R.id.twitter_view) RelativeLayout twitterView;
58 | @BindView(R.id.website_view) RelativeLayout webView;
59 | @BindView(R.id.dribbble_view) RelativeLayout dribbbleView;
60 | @BindView(R.id.blue_avatar) ImageView blurAvatar;
61 |
62 | private User mUser;
63 | private List shots;
64 | private ShotsAdapter mAdapter;
65 | private PlayerContract.IPlayerPresenter mPresenter;
66 | private ValueAnimator mAnimator;
67 | private String frontImageUrl;
68 | private AlertDialog.Builder mBuilder;
69 | private AlertDialog dialog;
70 | private int screenWidth;
71 |
72 | @Override protected void onCreate(Bundle savedInstanceState) {
73 | super.onCreate(savedInstanceState);
74 | setContentView(R.layout.activity_player);
75 | ButterKnife.bind(this);
76 |
77 | setSupportActionBar(mToolbar);
78 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
79 | getSupportActionBar().setDisplayShowTitleEnabled(false);
80 | mToolbar.setNavigationOnClickListener(view -> finish());
81 | init();
82 | }
83 |
84 | private void init() {
85 | mPresenter = new PlayerPresenter(this);
86 | mUser = (User) getIntent().getSerializableExtra("author");
87 | mRecyclerView.setLayoutManager(
88 | new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
89 | mRecyclerView.addItemDecoration(new ShotsAdapter.MyItemDecoration());
90 | mAnimator = ValueAnimator.ofFloat(1f, 0f);
91 | mAnimator.setDuration(200);
92 | mAnimator.setInterpolator(new DecelerateInterpolator());
93 | mAnimator.addUpdateListener(valueAnimator -> {
94 | float alpha = (float) valueAnimator.getAnimatedValue();
95 | mHoverView.setAlpha(alpha);
96 | frontImageView.setAlpha(alpha);
97 | });
98 | mAnimator.addListener(new Animator.AnimatorListener() {
99 | @Override public void onAnimationStart(Animator animator) {
100 |
101 | }
102 |
103 | @Override public void onAnimationEnd(Animator animator) {
104 | mHoverView.setVisibility(View.GONE);
105 | frontImageView.setVisibility(View.GONE);
106 | }
107 |
108 | @Override public void onAnimationCancel(Animator animator) {
109 |
110 | }
111 |
112 | @Override public void onAnimationRepeat(Animator animator) {
113 |
114 | }
115 | });
116 | frontImageView.setOnClickListener(view -> mAnimator.start());
117 | frontImageView.setOnLongClickListener(view -> {
118 | dialog.show();
119 | return true;
120 | });
121 | mHoverView.setOnClickListener(view -> mAnimator.start());
122 | if (mUser != null) {
123 | showPlayerInfo();
124 | } else {
125 | mPresenter.loadUserProfile();
126 | }
127 | mBuilder = new AlertDialog.Builder(this);
128 | mBuilder.setItems(new String[] { "保存图片" }, (DialogInterface dialogInterface, int i) -> {
129 | FileUtils.saveImage(this, frontImageUrl);
130 | });
131 | dialog = mBuilder.create();
132 | screenWidth = ScreenUtils.getScreenWidthAndHeight(getApplicationContext())[0];
133 | blurAvatar.setMinimumHeight(screenWidth);
134 |
135 | }
136 |
137 | @Override public void showPlayerInfo() {
138 | generateBlurAvatar();
139 | Glide.with(this).load(mUser.getAvatar_url()).crossFade().into(avatar);
140 | name.setText(mUser.getName());
141 | location.setText(mUser.getLocation());
142 | followers.setText(mUser.getFollowers_count() + "");
143 | followings.setText(mUser.getFollowings_count() + "");
144 | mPresenter.loadUserShots(mUser.getId());
145 | if (checkNotNUll(mUser.getLinks().getTwitter())) {
146 | twitterUrl.setText(mUser.getLinks().getTwitter());
147 | twitterLink.setOnClickListener(this);
148 | } else {
149 | twitterView.setVisibility(View.GONE);
150 | }
151 | if (checkNotNUll(mUser.getLinks().getWeb())) {
152 | websiteUrl.setText(mUser.getLinks().getWeb());
153 | browserLink.setOnClickListener(this);
154 | } else {
155 | webView.setVisibility(View.GONE);
156 | }
157 | if (checkNotNUll(mUser.getUsername())) {
158 | dribbbleUrl.setText(mUser.getUsername());
159 | dribbbleLink.setOnClickListener(this);
160 | } else {
161 | dribbbleView.setVisibility(View.GONE);
162 | }
163 | }
164 |
165 | private void generateBlurAvatar() {
166 | SimpleTarget target=new SimpleTarget() {
167 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) @Override
168 | public void onResourceReady(Bitmap resource, GlideAnimation super Bitmap> glideAnimation) {
169 | Bitmap blurImage= BitmapUtils.fastblur(resource,0.4f,8);
170 | runOnUiThread(()->{
171 | blurAvatar.setImageBitmap(blurImage);
172 | });
173 | }
174 | };
175 | Glide.with(this).load(mUser.getAvatar_url()).asBitmap().into(target);
176 | }
177 |
178 | @Override public void showPlayerInfoAsync(User user) {
179 | mUser = user;
180 | showPlayerInfo();
181 | }
182 |
183 | private boolean checkNotNUll(String str) {
184 | if (str == null) return false;
185 | if (str.trim().length() == 0) return false;
186 | return true;
187 | }
188 |
189 | void jumpToUrl(String url) {
190 | if (url == null) return;
191 | Intent intent = new Intent();
192 | intent.setAction(Intent.ACTION_VIEW);
193 | intent.setData(Uri.parse(url));
194 | startActivity(intent);
195 | }
196 |
197 | @Override public void showShots(final List shots) {
198 | this.shots = shots;
199 | mAdapter = new ShotsAdapter(shots, this);
200 | mAdapter.setListener(position -> {
201 | Shot shot = shots.get(position);
202 | frontImageUrl = shot.getImages().getHidpi();
203 | if (!StringUtils.checkAvailable(frontImageUrl)) {
204 | frontImageUrl = shot.getImages().getNormal();
205 | }
206 | if (!StringUtils.checkAvailable(frontImageUrl)) {
207 | frontImageUrl = shot.getImages().getTeaser();
208 | }
209 | Glide.with(PlayerActivity.this).load(frontImageUrl).crossFade().into(frontImageView);
210 | frontImageView.setAlpha(1f);
211 | mHoverView.setAlpha(1f);
212 | mHoverView.setVisibility(View.VISIBLE);
213 | frontImageView.setVisibility(View.VISIBLE);
214 | });
215 | mRecyclerView.setAdapter(mAdapter);
216 | mAdapter.notifyDataSetChanged();
217 | }
218 |
219 | @Override public void onClick(View view) {
220 | String url = null;
221 | switch (view.getId()) {
222 | case R.id.twitter_link:
223 | url = mUser.getLinks().getTwitter();
224 | break;
225 | case R.id.browser_link:
226 | url = mUser.getLinks().getWeb();
227 | break;
228 | case R.id.dribbble_link:
229 | url = mUser.getHtml_url();
230 | break;
231 | default:
232 | break;
233 | }
234 | jumpToUrl(url);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/browser.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/comments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/comments.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/dribbble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/dribbble.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/group.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/ic_account.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/ic_arrow.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/ic_menu.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/link.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/loading.gif
--------------------------------------------------------------------------------
/app/src/main/res/drawable/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/placeholder.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/round_rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/share.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/twitter.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/drawable/user.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_browse.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
24 |
25 |
26 |
36 |
37 |
38 |
45 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
27 |
28 |
29 |
34 |
35 |
40 |
41 |
42 |
43 |
50 |
59 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
81 |
82 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
28 |
29 |
41 |
42 |
56 |
57 |
58 |
59 |
65 |
70 |
71 |
80 |
81 |
82 |
83 |
90 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_player.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
17 |
26 |
27 |
28 |
34 |
38 |
43 |
50 |
51 |
61 |
62 |
72 |
73 |
83 |
84 |
85 |
86 |
93 |
100 |
110 |
118 |
126 |
127 |
128 |
135 |
145 |
153 |
161 |
162 |
163 |
164 |
165 |
179 |
180 |
188 |
189 |
190 |
197 |
204 |
215 |
216 |
226 |
238 |
239 |
246 |
257 |
258 |
268 |
280 |
281 |
288 |
298 |
308 |
309 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
333 |
334 |
342 |
343 |
344 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/author_and_shot_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
15 |
16 |
27 |
28 |
39 |
40 |
49 |
50 |
59 |
60 |
68 |
74 |
75 |
83 |
84 |
85 |
86 |
94 |
95 |
96 |
102 |
108 |
109 |
117 |
118 |
119 |
120 |
125 |
126 |
127 |
128 |
136 |
137 |
146 |
154 |
162 |
163 |
171 |
179 |
180 |
188 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/comment_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
27 |
28 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
12 |
13 |
14 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/internet_error_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/recyclerview_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/user_menu_item_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_browse.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/mipmap-xxhdpi/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2C2C2C
4 | #2E2E2E
5 | #FF4081
6 | #6A6A6A
7 | #DDDDDD
8 | #996A6A6A
9 | #CC000000
10 | #AA000000
11 | #EEEEEE
12 | #000000
13 | #3C3C3C
14 | #424242
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Designer
3 | Shared_Image
4 | Shared_Avatar
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/test/java/me/mrrobot97/designer/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package me.mrrobot97.designer;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.2.2'
10 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx2048m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrrobot97/Designer/d2e156f3613896b568329f52ea468a029b5b80df/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------