├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── ganhuo
│ │ └── app
│ │ ├── AboutActivity.java
│ │ ├── ApplicationPoint.java
│ │ ├── MainActivity.java
│ │ ├── WebViewActivity.java
│ │ ├── actions
│ │ └── DataController.java
│ │ ├── adapters
│ │ ├── ContentAdapterBase.java
│ │ └── EntryListAdapter.java
│ │ ├── fragments
│ │ ├── CommonFragment.java
│ │ ├── ContentFragmentBase.java
│ │ └── EntryListFragment.java
│ │ ├── holders
│ │ ├── EntryViewHolder.java
│ │ └── FooterViewHolder.java
│ │ ├── interfaces
│ │ ├── AdapterRespondent.java
│ │ ├── DataProvider.java
│ │ ├── SimpleUIRespondent.java
│ │ └── UIRespondent.java
│ │ ├── models
│ │ └── Entry.java
│ │ └── providers
│ │ └── EntryListProvider.java
│ └── res
│ ├── layout
│ ├── activity_about.xml
│ ├── activity_main.xml
│ ├── entry_item.xml
│ ├── fragment_content.xml
│ ├── recyclerview_footer.xml
│ ├── title.xml
│ └── webview.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── app_logo.png
│ ├── mipmap-mdpi
│ └── app_logo.png
│ ├── mipmap-xhdpi
│ └── app_logo.png
│ ├── mipmap-xxhdpi
│ └── app_logo.png
│ ├── mipmap-xxxhdpi
│ └── app_logo.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | .DS_Store
5 | /build
6 | # built application files
7 | *.apk
8 | *.ap_
9 |
10 | # files for the dex VM
11 | *.dex
12 |
13 | # Java class files
14 | *.class
15 | .DS_Store
16 |
17 | # generated files
18 | bin/
19 | gen/
20 | Wiki/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Eclipse project files
26 | .classpath
27 | .project
28 | .settings/
29 |
30 | # Proguard folder generated by Eclipse
31 | proguard/
32 |
33 | #Android Studio
34 | build/
35 |
36 | # Intellij project files
37 | *.iml
38 | *.ipr
39 | *.iws
40 | .idea/
41 |
42 | #gradle
43 | .gradle/
44 | captures
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 干货集中营
2 |
3 | ## 这是什么?
4 |
5 | 有一天,我发现了好多开发上的干货,我觉得这些干货好有用,我好想让别人知道,这个干货好有用。所以我就建了个邮件列表,
6 | 开始给大家推荐每日开发干货。订阅地址是: http://tinyletter.com/daimajia
7 |
8 | ## 为什么要做这个?
9 |
10 | 又有一天,我发现很多 QQ 订阅用户收不到干货,我说,这不行,我不答应,每个人都应该有收到干货的权利!所以我觉得是时候写个 App 了。
11 | 就这样,这个仓储就被创建了!
12 |
13 | ## 为什么要开源?
14 |
15 | 我的一贯作风。
16 |
17 | ## 我要怎么贡献?
18 |
19 | Fork ---> Pull Request.
20 |
21 | ## 我有意见和建议怎么办?
22 |
23 | 发我邮箱: daimajia@gmail.com
24 |
25 | 或者
26 |
27 | 加我微信:daimajia 记着加备注
28 |
29 | ## iOS 版本有么?
30 |
31 | 有的,点[这里](https://github.com/ganhuo/iOS-Ganhuo)!
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.ganhuo.app"
9 | minSdkVersion 14
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 |
21 | packagingOptions {
22 | exclude 'META-INF/LICENSE.txt'
23 | exclude 'META-INF/NOTICE.txt'
24 | }
25 |
26 | lintOptions {
27 | abortOnError false
28 | }
29 |
30 | sourceSets {
31 | main {
32 | jniLibs.srcDirs = ['libs']
33 | }
34 | }
35 | }
36 |
37 | dependencies {
38 | compile fileTree(dir: 'libs', include: ['*.jar'])
39 | compile 'com.android.support:appcompat-v7:22.1.1'
40 | compile 'com.android.support:recyclerview-v7:21.0.3'
41 | compile 'com.android.support:cardview-v7:21.0.3'
42 | compile 'com.github.bumptech.glide:glide:3.5.1'
43 | compile 'com.daimajia.numberprogressbar:library:1.2@aar'
44 | compile 'com.daimajia.easing:library:1.0.1@aar'
45 | compile 'com.daimajia.androidanimations:library:1.1.3@aar'
46 | compile 'im.fir:sdk:1.1.1'
47 | compile 'com.nineoldandroids:library:2.4.0'
48 | compile 'cn.leancloud.android:avoscloud-sdk:v3.1.1'
49 | compile 'cn.leancloud.android:Java-WebSocket:1.2.0-leancloud'
50 | compile 'cn.leancloud.android:avoscloud-statistics:v3.1@aar'
51 | compile 'cn.leancloud.android:avoscloud-push:v3.1.5@aar'
52 | compile('com.mikepenz.aboutlibraries:library:4.6.6@aar') {
53 | transitive = true
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/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/daimajia/Development/Environment/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/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | public class AboutActivity extends AppCompatActivity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_about);
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/ApplicationPoint.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app;
2 |
3 | import android.app.Application;
4 |
5 | import com.avos.avoscloud.AVInstallation;
6 | import com.avos.avoscloud.AVOSCloud;
7 | import com.avos.avoscloud.AVObject;
8 | import com.avos.avoscloud.PushService;
9 | import com.ganhuo.app.models.Entry;
10 |
11 | import im.fir.sdk.FIR;
12 |
13 | public class ApplicationPoint extends Application {
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | FIR.init(this);
18 | AVObject.registerSubclass(Entry.class);
19 | AVOSCloud.initialize(getApplicationContext(), "id77coqgtrm74f6esc617hhqxe4bo8icg17al5998lcdxswy", "qoj2a3fo8icot1qigiemnd36zjdfir45d9qapxna6ky7l0gs");
20 | AVInstallation.getCurrentInstallation().saveInBackground();
21 | PushService.setDefaultPushCallback(this, MainActivity.class);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.Menu;
7 | import android.view.MenuItem;
8 |
9 | import com.avos.avoscloud.AVAnalytics;
10 | import com.ganhuo.app.fragments.EntryListFragment;
11 |
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 | getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new EntryListFragment()).commit();
20 | Intent intent = getIntent();
21 | AVAnalytics.trackAppOpened(intent);
22 | }
23 |
24 | @Override
25 | public boolean onCreateOptionsMenu(Menu menu) {
26 | getMenuInflater().inflate(R.menu.menu_main, menu);
27 | return true;
28 | }
29 |
30 | @Override
31 | public boolean onOptionsItemSelected(MenuItem item) {
32 | int id = item.getItemId();
33 | if (id == R.id.about) {
34 | startActivity(new Intent(this, AboutActivity.class));
35 | return true;
36 | }
37 |
38 | return super.onOptionsItemSelected(item);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/WebViewActivity.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBarActivity;
6 | import android.view.KeyEvent;
7 | import android.view.MenuItem;
8 | import android.webkit.WebChromeClient;
9 | import android.webkit.WebView;
10 | import android.webkit.WebViewClient;
11 |
12 | import com.daimajia.androidanimations.library.Techniques;
13 | import com.daimajia.androidanimations.library.YoYo;
14 | import com.daimajia.numberprogressbar.NumberProgressBar;
15 |
16 | public class WebViewActivity extends ActionBarActivity {
17 |
18 | private WebView mWebView;
19 | private WebClient mWebClient;
20 | private NumberProgressBar mProgressbar;
21 |
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | setContentView(R.layout.webview);
27 | String url = getIntent().getStringExtra("url");
28 | mWebClient = new WebClient();
29 | mWebView = (WebView) findViewById(R.id.webview);
30 | mProgressbar = (NumberProgressBar) findViewById(R.id.progressbar);
31 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
32 | mWebView.setWebViewClient(mWebClient);
33 | mWebView.getSettings().setJavaScriptEnabled(true);
34 | mWebView.setWebChromeClient(new WebChrome());
35 | mWebView.getSettings().setBuiltInZoomControls(true);
36 | mWebView.getSettings().setDisplayZoomControls(false);
37 | mWebView.loadUrl(url);
38 | }
39 |
40 | @Override
41 | public boolean onKeyDown(int keyCode, KeyEvent event) {
42 | if (event.getAction() == KeyEvent.ACTION_DOWN) {
43 | switch (keyCode) {
44 | case KeyEvent.KEYCODE_BACK:
45 | if (mWebView.canGoBack()) {
46 | mWebView.goBack();
47 | } else {
48 | finish();
49 | }
50 | return true;
51 | }
52 |
53 | }
54 | return super.onKeyDown(keyCode, event);
55 | }
56 |
57 |
58 | @Override
59 | public boolean onOptionsItemSelected(MenuItem item) {
60 | finish();
61 | return super.onOptionsItemSelected(item);
62 | }
63 |
64 | @Override
65 | protected void onDestroy() {
66 | super.onDestroy();
67 | if (mWebView != null)
68 | mWebView.destroy();
69 | }
70 |
71 | @Override
72 | protected void onPause() {
73 | super.onPause();
74 | if (mWebView != null)
75 | mWebView.onPause();
76 | }
77 |
78 | @Override
79 | protected void onResume() {
80 | super.onResume();
81 | mWebView.onResume();
82 | }
83 |
84 | private class WebClient extends WebViewClient {
85 | @Override
86 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
87 | return false;
88 | }
89 |
90 | @Override
91 | public void onPageFinished(WebView view, String url) {
92 |
93 | }
94 |
95 | @Override
96 | public void onPageStarted(WebView view, String url, Bitmap favicon) {
97 | super.onPageStarted(view, url, favicon);
98 | YoYo.with(Techniques.FadeIn).playOn(mProgressbar);
99 | }
100 |
101 | @Override
102 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
103 | super.onReceivedError(view, errorCode, description, failingUrl);
104 | }
105 |
106 | }
107 |
108 | private class WebChrome extends WebChromeClient {
109 | @Override
110 | public void onProgressChanged(WebView view, int newProgress) {
111 | super.onProgressChanged(view, newProgress);
112 | mProgressbar.setProgress(newProgress);
113 | }
114 |
115 | @Override
116 | public void onReceivedTitle(WebView view, String title) {
117 | super.onReceivedTitle(view, title);
118 | }
119 | }
120 |
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/actions/DataController.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.actions;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.os.Message;
6 |
7 | import com.ganhuo.app.interfaces.AdapterRespondent;
8 | import com.ganhuo.app.interfaces.DataProvider;
9 | import com.ganhuo.app.interfaces.UIRespondent;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | public abstract class DataController implements DataProvider {
15 |
16 | private List mRepository;
17 |
18 | private boolean isBusy = false;
19 | private boolean isEnd = false;
20 | private int mPageSize = 10;
21 | private int mNextTimeOffset = 0;
22 | private int mRequestTimes = 0;
23 |
24 | private List> mUIRespondents;
25 | private List mAdapterRespondents;
26 |
27 | public boolean isBusy() {
28 | return isBusy;
29 | }
30 |
31 | public boolean isEmpty() {
32 | return mRepository == null || mRepository.size() == 0;
33 | }
34 |
35 | public boolean isEnd() {
36 | return isEnd;
37 | }
38 |
39 | public int getSize() {
40 | if (mRepository == null) {
41 | return 0;
42 | }
43 | return mRepository.size();
44 | }
45 |
46 | public int getRequestOffset() {
47 | return mNextTimeOffset;
48 | }
49 |
50 | public int getPageSize() {
51 | return mPageSize;
52 | }
53 |
54 | public void setPageSize(int pageSize) {
55 | mPageSize = pageSize;
56 | }
57 |
58 | public List getData() throws Exception {
59 | return mRepository;
60 | }
61 |
62 | public void clear() {
63 | if (mRepository != null) {
64 | mRepository.clear();
65 | mNextTimeOffset = 0;
66 | mRequestTimes = 0;
67 | }
68 | }
69 |
70 | public void add(T data) {
71 | if (data != null) {
72 | mRepository.add(0, data);
73 | dispatchAdapterMessage(AdapterMessageType.INSERT, 0);
74 | }
75 | }
76 |
77 | public void append(T data) {
78 | if (data != null) {
79 | mRepository.add(data);
80 | dispatchAdapterMessage(AdapterMessageType.INSERT, mRepository.size() - 1);
81 | }
82 | }
83 |
84 | public void add(List data) {
85 | if (data == null) {
86 | isEnd = true;
87 | return;
88 | }
89 | if (mRepository == null) {
90 | mRepository = new ArrayList<>();
91 | }
92 | if (data.size() < mPageSize) {
93 | isEnd = true;
94 | }
95 | mRepository.addAll(0, data);
96 | }
97 |
98 | public void append(List data) {
99 | if (data == null) {
100 | isEnd = true;
101 | return;
102 | }
103 | if (mRepository == null) {
104 | mRepository = new ArrayList<>();
105 | }
106 | if (data.size() < mPageSize) {
107 | isEnd = true;
108 | }
109 | mRepository.addAll(data);
110 | }
111 |
112 | public void addUIRespondent(UIRespondent respondent) {
113 | if (mUIRespondents == null) {
114 | mUIRespondents = new ArrayList<>();
115 | }
116 | mUIRespondents.add(respondent);
117 | }
118 |
119 | public void addAdapterRespondent(AdapterRespondent respondent) {
120 | if (mAdapterRespondents == null) {
121 | mAdapterRespondents = new ArrayList<>();
122 | }
123 | mAdapterRespondents.add(respondent);
124 | }
125 |
126 | public final void initialize() {
127 | doAction(ActionType.Initialize);
128 | }
129 |
130 | public final void refresh() {
131 | doAction(ActionType.Refresh);
132 | }
133 |
134 | public final void more() {
135 | doAction(ActionType.More);
136 | }
137 |
138 | protected void doAction(final ActionType type) {
139 | if (!type.equals(ActionType.Refresh)) {
140 | if ((isEnd || isBusy)) {
141 | return;
142 | }
143 | }
144 | isBusy = true;
145 | switch (type) {
146 | case Initialize:
147 | dispatchUIMessage(UIMessageType.InitializeStart, null, null);
148 | break;
149 | case More:
150 | dispatchUIMessage(UIMessageType.LoadingMoreStart, null, null);
151 | break;
152 | case Refresh:
153 | dispatchUIMessage(UIMessageType.RefreshingStart, null, null);
154 | break;
155 | }
156 |
157 |
158 | new Thread(new Runnable() {
159 | @Override
160 | public void run() {
161 | List data = null;
162 | Exception exception = null;
163 | try {
164 | switch (type) {
165 | case Initialize:
166 | data = doInitialize();
167 | add(data);
168 | break;
169 | case More:
170 | data = doMore();
171 | append(data);
172 | break;
173 | case Refresh:
174 | data = doRefresh();
175 | add(data);
176 | break;
177 | }
178 | mRequestTimes++;
179 | mNextTimeOffset = mPageSize * mRequestTimes;
180 | } catch (Exception e) {
181 | exception = e;
182 | e.printStackTrace();
183 | } finally {
184 | if (exception == null)
185 | dispatchAdapterMessage(AdapterMessageType.CHANGE, 0);
186 |
187 | switch (type) {
188 | case Initialize:
189 | dispatchUIMessage(UIMessageType.InitializeDone, exception, data);
190 | break;
191 | case More:
192 | dispatchUIMessage(UIMessageType.LoadMoreDone, exception, data);
193 | break;
194 | case Refresh:
195 | dispatchUIMessage(UIMessageType.RefreshingDone, exception, data);
196 | break;
197 | }
198 | if (isEnd) {
199 | dispatchUIMessage(UIMessageType.End, null, null);
200 | }
201 | isBusy = false;
202 | }
203 | }
204 | }).start();
205 | }
206 |
207 |
208 | private void dispatchAdapterMessage(AdapterMessageType type, int position) {
209 | Handler handler = new AdapterPosterHandler(Looper.getMainLooper());
210 | Message msg = Message.obtain(handler);
211 | msg.arg1 = type.ordinal();
212 | msg.arg2 = position;
213 | msg.sendToTarget();
214 | }
215 |
216 | private void dispatchUIMessage(UIMessageType type, Exception e, List data) {
217 | if (mUIRespondents == null || mUIRespondents.size() == 0) {
218 | return;
219 | }
220 | Handler handler = new UIPosterHandler(Looper.getMainLooper());
221 | Message msg = Message.obtain(handler);
222 | msg.arg1 = type.ordinal();
223 | msg.obj = data != null ? data : e;
224 | msg.sendToTarget();
225 | }
226 |
227 | public T getData(int position) {
228 | return mRepository.get(position);
229 | }
230 |
231 |
232 | protected enum ActionType {
233 | Initialize,
234 | More,
235 | Refresh
236 | }
237 |
238 | public enum AdapterMessageType {
239 | INSERT,
240 | CHANGE, REMOVE
241 | }
242 |
243 | public enum UIMessageType {
244 | OnDataSetChanged,
245 | InitializeStart,
246 | InitializeDone,
247 | LoadingMoreStart,
248 | LoadMoreDone,
249 | RefreshingStart,
250 | RefreshingDone,
251 | End
252 | }
253 |
254 | public class AdapterPosterHandler extends Handler {
255 |
256 | public AdapterPosterHandler(Looper looper) {
257 | super(looper);
258 | }
259 |
260 | @Override
261 | public void handleMessage(Message msg) {
262 | for (AdapterRespondent adapterRespondent : mAdapterRespondents) {
263 | AdapterMessageType type = AdapterMessageType.values()[msg.arg1];
264 | switch (type) {
265 | case INSERT:
266 | adapterRespondent.onDataInsert(msg.arg1);
267 | break;
268 | case REMOVE:
269 | adapterRespondent.onDataRemove(msg.arg2);
270 | break;
271 | case CHANGE:
272 | adapterRespondent.onDataChanged();
273 | break;
274 | }
275 |
276 | }
277 | }
278 |
279 | }
280 |
281 |
282 | public class UIPosterHandler extends Handler {
283 |
284 | private UIPosterHandler(Looper looper) {
285 | super(looper);
286 | }
287 |
288 |
289 | @Override
290 | public void handleMessage(Message msg) {
291 | if (getLooper() != Looper.getMainLooper()) {
292 | throw new RuntimeException("Can not post UI update in non-main thread");
293 | }
294 | for (UIRespondent ui : mUIRespondents) {
295 | UIMessageType type = UIMessageType.values()[msg.arg1];
296 | Object o = msg.obj;
297 | List data = o instanceof List ? (List) o : null;
298 | Exception e = o instanceof Exception ? (Exception) o : null;
299 | switch (type) {
300 | case InitializeStart:
301 | ui.onInitializeStart();
302 | break;
303 | case InitializeDone:
304 | ui.onInitializeDone(e, data);
305 | break;
306 | case LoadingMoreStart:
307 | ui.onLoadingMoreStart();
308 | break;
309 | case LoadMoreDone:
310 | ui.onLoadMoreDone(e, data);
311 | break;
312 | case RefreshingStart:
313 | ui.onRefreshingStart();
314 | break;
315 | case RefreshingDone:
316 | ui.onRefreshDone(e, data);
317 | break;
318 | case End:
319 | ui.onEnd();
320 | break;
321 | }
322 | }
323 | }
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/adapters/ContentAdapterBase.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.adapters;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Button;
9 | import android.widget.ProgressBar;
10 | import android.widget.TextView;
11 |
12 |
13 | import com.ganhuo.app.R;
14 | import com.ganhuo.app.actions.DataController;
15 | import com.ganhuo.app.holders.FooterViewHolder;
16 | import com.ganhuo.app.interfaces.AdapterRespondent;
17 | import com.ganhuo.app.interfaces.SimpleUIRespondent;
18 |
19 | import java.util.List;
20 |
21 | public abstract class ContentAdapterBase extends RecyclerView.Adapter {
22 |
23 | protected Context mContext;
24 | protected LayoutInflater mInflater;
25 | protected DataController mDataController;
26 | protected View mLoadingMoreView;
27 |
28 | public ContentAdapterBase(Context mContext, final DataController mDataController) {
29 | this.mContext = mContext;
30 | this.mDataController = mDataController;
31 | this.mInflater = LayoutInflater.from(mContext);
32 | this.mDataController.addAdapterRespondent(new AdapterRespondent() {
33 | @Override
34 | public void onDataChanged() {
35 | notifyDataSetChanged();
36 | }
37 |
38 | @Override
39 | public void onDataInsert(int position) {
40 | notifyDataSetChanged();
41 | }
42 |
43 | @Override
44 | public void onDataRemove(int position) {
45 | notifyItemRemoved(position);
46 | }
47 | });
48 | this.mDataController.addUIRespondent(new DataObserver());
49 | }
50 |
51 | @Override
52 | public int getItemViewType(int position) {
53 | if (needHeader() && position == 0) {
54 | return CommonFeature.HEADER.ordinal();
55 | }
56 |
57 | if (position == getItemCount() - 1) {
58 | return CommonFeature.FOOTER.ordinal();
59 | }
60 |
61 | return CommonFeature.COMMON.ordinal();
62 | }
63 |
64 | @Override
65 | public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
66 | if (viewType == CommonFeature.FOOTER.ordinal()) {
67 | mLoadingMoreView = mInflater.inflate(R.layout.recyclerview_footer, parent, false);
68 | return new FooterViewHolder(mLoadingMoreView);
69 | } else if (viewType == CommonFeature.HEADER.ordinal()) {
70 | return onCreateCustomHeaderHolder(parent);
71 | } else {
72 | return onCreateCustomContentHolder(parent, viewType);
73 | }
74 | }
75 |
76 | @Override
77 | public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
78 | if (needHeader() && position == 0) {
79 | onBindCustomHeaderHolder(holder);
80 | return;
81 | }
82 |
83 | if (needHeader()) {
84 | position -= 1;
85 | }
86 |
87 | if (holder instanceof FooterViewHolder) {
88 | FooterViewHolder footerViewHolder = (FooterViewHolder) holder;
89 | if (mDataController.isEnd()) {
90 | footerViewHolder.onBindViewHolder();
91 | } else {
92 | mDataController.more();
93 | }
94 | } else {
95 | onBindCustomViewHolder(holder, position);
96 | }
97 |
98 | }
99 |
100 | protected RecyclerView.ViewHolder onCreateCustomHeaderHolder(ViewGroup parent) {
101 | return null;
102 | }
103 |
104 | protected void onBindCustomHeaderHolder(RecyclerView.ViewHolder holder) {
105 |
106 | }
107 |
108 | protected abstract RecyclerView.ViewHolder onCreateCustomContentHolder(ViewGroup parent, int viewType);
109 |
110 |
111 | protected abstract void onBindCustomViewHolder(RecyclerView.ViewHolder holder, int position);
112 |
113 | protected boolean needHeader() {
114 | return false;
115 | }
116 |
117 |
118 | @Override
119 | public void onAttachedToRecyclerView(RecyclerView recyclerView) {
120 | super.onAttachedToRecyclerView(recyclerView);
121 | mDataController.initialize();
122 | }
123 |
124 | @Override
125 | public int getItemCount() {
126 | if (mDataController.getSize() == 0) {
127 | if (needHeader()) {
128 | return 1;
129 | } else {
130 | return 0;
131 | }
132 | }
133 | if (needHeader()) {
134 | return mDataController.getSize() + 2;
135 | }
136 |
137 | return mDataController.getSize() + 1;
138 | }
139 |
140 | public enum CommonFeature{
141 | HEADER,
142 | COMMON,
143 | FOOTER
144 | }
145 |
146 | private class DataObserver extends SimpleUIRespondent {
147 | @Override
148 | public void onLoadMoreDone(Exception e, List data) {
149 | if (e != null && mLoadingMoreView != null) {
150 | final Button refresh = (Button) mLoadingMoreView.findViewById(R.id.loading_more_retry);
151 | final TextView tips = (TextView) mLoadingMoreView.findViewById(R.id.loading_more_tips);
152 | final ProgressBar progressBar = (ProgressBar) mLoadingMoreView.findViewById(R.id.loading_more_progress);
153 | progressBar.setVisibility(View.INVISIBLE);
154 | refresh.setVisibility(View.VISIBLE);
155 | tips.setVisibility(View.VISIBLE);
156 | refresh.setOnClickListener(new View.OnClickListener() {
157 | @Override
158 | public void onClick(View v) {
159 | refresh.setVisibility(View.INVISIBLE);
160 | tips.setVisibility(View.INVISIBLE);
161 | progressBar.setVisibility(View.VISIBLE);
162 | mDataController.more();
163 | }
164 | });
165 | }
166 | }
167 | }
168 | }
169 |
170 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/adapters/EntryListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.adapters;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.ViewGroup;
6 |
7 | import com.ganhuo.app.actions.DataController;
8 | import com.ganhuo.app.holders.EntryViewHolder;
9 | import com.ganhuo.app.models.Entry;
10 |
11 | public class EntryListAdapter extends ContentAdapterBase {
12 |
13 | private Context mContext;
14 |
15 | public EntryListAdapter(Context context, DataController dataController) {
16 | super(context, dataController);
17 | this.mContext = context;
18 | }
19 |
20 |
21 | @Override
22 | protected RecyclerView.ViewHolder onCreateCustomContentHolder(ViewGroup parent, int viewType) {
23 | return EntryViewHolder.create(mContext, parent);
24 | }
25 |
26 | @Override
27 | protected void onBindCustomViewHolder(RecyclerView.ViewHolder holder, int position) {
28 | ((EntryViewHolder) holder).onBindViewHolder(mDataController.getData(position));
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/fragments/CommonFragment.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v4.widget.ContentLoadingProgressBar;
6 | import android.support.v4.widget.SwipeRefreshLayout;
7 | import android.support.v7.widget.LinearLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import com.ganhuo.app.R;
17 | import com.ganhuo.app.actions.DataController;
18 |
19 | import java.util.List;
20 |
21 | public abstract class CommonFragment extends ContentFragmentBase {
22 | protected RecyclerView mRecyclerView;
23 | protected ViewGroup mRoot, mPagerPage, mLoadingPage;
24 | protected ContentLoadingProgressBar mLoadingBar;
25 |
26 | protected TextView mLoadingTips;
27 | protected Button mLoadingRetry;
28 |
29 | protected SwipeRefreshLayout mSwipeRefresh;
30 | protected RecyclerView.Adapter mAdapter;
31 | protected DataController mDataController;
32 |
33 |
34 | @Override
35 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
36 | ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_content, container, false);
37 | mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview_content);
38 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
39 | mPagerPage = (ViewGroup) rootView.findViewById(R.id.pager_page);
40 | mLoadingPage = (ViewGroup) rootView.findViewById(R.id.loading_page);
41 | mLoadingBar = (ContentLoadingProgressBar) mLoadingPage.findViewById(R.id.loading_bar);
42 | mLoadingRetry = (Button) mLoadingPage.findViewById(R.id.loading_refresh);
43 | mLoadingTips = (TextView) mLoadingPage.findViewById(R.id.loading_tips);
44 | mSwipeRefresh = (SwipeRefreshLayout) mPagerPage;
45 | mRoot = rootView;
46 | if (enableRefresh()) {
47 | mSwipeRefresh.setOnRefreshListener(this);
48 | } else {
49 | mSwipeRefresh.setEnabled(false);
50 | }
51 | return rootView;
52 | }
53 |
54 | @Override
55 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
56 | mDataController = onGenerateDataController();
57 | mAdapter = onGenerateAdapter(mDataController);
58 | mDataController.addUIRespondent(this);
59 | mDataController.addAdapterRespondent(this);
60 | mRecyclerView.setAdapter(mAdapter);
61 | }
62 |
63 | protected abstract DataController onGenerateDataController();
64 |
65 | protected abstract RecyclerView.Adapter onGenerateAdapter(DataController controller);
66 |
67 | protected abstract boolean enableRefresh();
68 |
69 | @Override
70 | public void onDataChanged() {
71 | mAdapter.notifyDataSetChanged();
72 | }
73 |
74 | @Override
75 | public void onDataInsert(int position) {
76 | mAdapter.notifyItemInserted(position);
77 | }
78 |
79 | @Override
80 | public void onDataRemove(int position) {
81 | mAdapter.notifyItemRemoved(position);
82 | }
83 |
84 | @Override
85 | public void onRefreshingStart() {
86 | super.onRefreshingStart();
87 | mSwipeRefresh.setRefreshing(true);
88 | }
89 |
90 | @Override
91 | public void onRefreshDone(Exception e, List data) {
92 | if (e == null) {
93 | if (data == null || data.size() == 0) {
94 | Toast.makeText(getActivity(), "没啦,干货正在准备中呢!", Toast.LENGTH_SHORT).show();
95 | } else {
96 | Toast.makeText(getActivity(), "成功挖掘 " + data.size() + " 条新干货", Toast.LENGTH_SHORT).show();
97 | }
98 | } else {
99 | e.printStackTrace();
100 | Toast.makeText(getActivity(), "出现了些小问题", Toast.LENGTH_SHORT).show();
101 | }
102 | mSwipeRefresh.setRefreshing(false);
103 | }
104 |
105 | @Override
106 | public void onInitializeDone(Exception e, List data) {
107 | super.onInitializeDone(e, data);
108 | mLoadingBar.hide();
109 | if (e == null) {
110 | mRoot.bringChildToFront(mPagerPage);
111 | } else {
112 | mLoadingRetry.setVisibility(View.VISIBLE);
113 | mLoadingTips.setVisibility(View.VISIBLE);
114 | mLoadingRetry.setOnClickListener(new View.OnClickListener() {
115 | @Override
116 | public void onClick(View v) {
117 | mLoadingRetry.setVisibility(View.GONE);
118 | mLoadingTips.setVisibility(View.GONE);
119 | mDataController.initialize();
120 | }
121 | });
122 | mLoadingTips.setVisibility(View.VISIBLE);
123 | e.printStackTrace();
124 | }
125 | }
126 |
127 | @Override
128 | public void onRefresh() {
129 | mDataController.refresh();
130 | }
131 |
132 | }
133 |
134 |
135 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/fragments/ContentFragmentBase.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.fragments;
2 |
3 | import android.support.v4.app.Fragment;
4 | import android.support.v4.widget.SwipeRefreshLayout;
5 |
6 | import com.ganhuo.app.interfaces.AdapterRespondent;
7 | import com.ganhuo.app.interfaces.UIRespondent;
8 |
9 | import java.util.List;
10 |
11 | public abstract class ContentFragmentBase extends Fragment implements UIRespondent, AdapterRespondent, SwipeRefreshLayout.OnRefreshListener {
12 |
13 |
14 | @Override
15 | public void onInitializeStart() {
16 |
17 | }
18 |
19 | @Override
20 | public void onInitializeDone(Exception e, List data) {
21 |
22 | }
23 |
24 | @Override
25 | public void onLoadingMoreStart() {
26 |
27 | }
28 |
29 | @Override
30 | public void onLoadMoreDone(Exception e, List data) {
31 |
32 | }
33 |
34 | @Override
35 | public void onRefreshingStart() {
36 |
37 | }
38 |
39 | @Override
40 | public void onRefreshDone(Exception e, List data) {
41 |
42 | }
43 |
44 | @Override
45 | public void onEnd() {
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/fragments/EntryListFragment.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.fragments;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 |
5 | import com.ganhuo.app.actions.DataController;
6 | import com.ganhuo.app.adapters.EntryListAdapter;
7 | import com.ganhuo.app.models.Entry;
8 | import com.ganhuo.app.providers.EntryListProvider;
9 |
10 | public class EntryListFragment extends CommonFragment {
11 | @Override
12 | protected DataController onGenerateDataController() {
13 | return new EntryListProvider();
14 | }
15 |
16 | @Override
17 | protected RecyclerView.Adapter onGenerateAdapter(DataController controller) {
18 | return new EntryListAdapter(getActivity(), controller);
19 | }
20 |
21 | @Override
22 | protected boolean enableRefresh() {
23 | return true;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/holders/EntryViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.holders;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
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 android.widget.TextView;
11 |
12 | import com.bumptech.glide.Glide;
13 | import com.ganhuo.app.R;
14 | import com.ganhuo.app.WebViewActivity;
15 | import com.ganhuo.app.models.Entry;
16 |
17 | public class EntryViewHolder extends RecyclerView.ViewHolder {
18 | public final ImageView thumb;
19 | public final TextView title, content, time;
20 | public final View parent;
21 |
22 | private EntryViewHolder(View itemView) {
23 | super(itemView);
24 | thumb = (ImageView) itemView.findViewById(R.id.thumb);
25 | title = (TextView) itemView.findViewById(R.id.title);
26 | content = (TextView) itemView.findViewById(R.id.content);
27 | time = (TextView) itemView.findViewById(R.id.time);
28 | parent = itemView;
29 | }
30 |
31 | public static EntryViewHolder create(final Context context, ViewGroup parent) {
32 | View v = LayoutInflater.from(context).inflate(R.layout.entry_item, parent, false);
33 | v.setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View v) {
36 | Entry entry = (Entry) v.getTag();
37 | if (entry != null) {
38 | Intent intent = new Intent(context, WebViewActivity.class);
39 | intent.putExtra("url", entry.getUrl());
40 | context.startActivity(intent);
41 | }
42 | }
43 | });
44 | return new EntryViewHolder(v);
45 | }
46 |
47 | public void onBindViewHolder(Entry entry) {
48 | parent.setTag(entry);
49 | title.setText(entry.getTitle());
50 | content.setText(entry.getContent());
51 | time.setText(entry.getBeautyPublishDate());
52 | Glide.with(thumb.getContext()).load(entry.getThumbUrl()).into(thumb);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/holders/FooterViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.holders;
2 |
3 | import android.support.v4.widget.ContentLoadingProgressBar;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.View;
6 | import android.widget.TextView;
7 |
8 | import com.ganhuo.app.R;
9 |
10 |
11 | public class FooterViewHolder extends RecyclerView.ViewHolder {
12 | public final ContentLoadingProgressBar progressBar;
13 | public final TextView tips;
14 |
15 | public FooterViewHolder(View itemView) {
16 | super(itemView);
17 | progressBar = (ContentLoadingProgressBar) itemView.findViewById(R.id.loading_more_progress);
18 | tips = (TextView) itemView.findViewById(R.id.loading_more_tips);
19 | }
20 |
21 | public void onBindViewHolder() {
22 | tips.setText("到头了");
23 | tips.setVisibility(View.VISIBLE);
24 | progressBar.hide();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/interfaces/AdapterRespondent.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.interfaces;
2 |
3 | public interface AdapterRespondent {
4 | void onDataChanged();
5 |
6 | void onDataInsert(int position);
7 |
8 | void onDataRemove(int position);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/interfaces/DataProvider.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.interfaces;
2 |
3 | import java.util.List;
4 |
5 | public interface DataProvider {
6 | List doInitialize() throws Exception;
7 |
8 | List doRefresh() throws Exception;
9 |
10 | List doMore() throws Exception;
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/interfaces/SimpleUIRespondent.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.interfaces;
2 |
3 | import java.util.List;
4 |
5 | public class SimpleUIRespondent implements UIRespondent {
6 | @Override
7 | public void onInitializeStart() {
8 |
9 | }
10 |
11 | @Override
12 | public void onInitializeDone(Exception e, List data) {
13 |
14 | }
15 |
16 | @Override
17 | public void onLoadingMoreStart() {
18 |
19 | }
20 |
21 | @Override
22 | public void onLoadMoreDone(Exception e, List data) {
23 |
24 | }
25 |
26 | @Override
27 | public void onRefreshingStart() {
28 |
29 | }
30 |
31 | @Override
32 | public void onRefreshDone(Exception e, List data) {
33 |
34 | }
35 |
36 | @Override
37 | public void onEnd() {
38 |
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/interfaces/UIRespondent.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.interfaces;
2 |
3 | import java.util.List;
4 |
5 | public interface UIRespondent {
6 |
7 | void onInitializeStart();
8 |
9 | void onInitializeDone(Exception e, List data);
10 |
11 | void onLoadingMoreStart();
12 |
13 | void onLoadMoreDone(Exception e, List data);
14 |
15 | void onRefreshingStart();
16 |
17 | void onRefreshDone(Exception e, List data);
18 |
19 | void onEnd();
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/models/Entry.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.models;
2 |
3 | import android.text.format.DateFormat;
4 |
5 | import com.avos.avoscloud.AVClassName;
6 | import com.avos.avoscloud.AVFile;
7 | import com.avos.avoscloud.AVObject;
8 |
9 | import java.util.Date;
10 |
11 | @AVClassName("Entry")
12 | public class Entry extends AVObject {
13 | public String getTitle() {
14 | return getString("title");
15 | }
16 |
17 | public String getContent() {
18 | return getString("content");
19 | }
20 |
21 | public String getUrl() {
22 | return getString("url");
23 | }
24 |
25 | public AVFile getThumb() {
26 | return getAVFile("thumb");
27 | }
28 |
29 |
30 | public Date getPublishDate() {
31 | return getDate("publishDate");
32 | }
33 |
34 | public String getBeautyPublishDate() {
35 | return DateFormat.format("yyyy-MM-dd", getPublishDate()).toString();
36 | }
37 |
38 | public String getThumbUrl() {
39 | return getThumb().getUrl();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ganhuo/app/providers/EntryListProvider.java:
--------------------------------------------------------------------------------
1 | package com.ganhuo.app.providers;
2 |
3 | import com.avos.avoscloud.AVQuery;
4 | import com.ganhuo.app.actions.DataController;
5 | import com.ganhuo.app.models.Entry;
6 |
7 | import java.util.List;
8 |
9 | public class EntryListProvider extends DataController {
10 |
11 |
12 | public List getData() throws Exception {
13 | AVQuery query = new AVQuery<>("Entry");
14 | query.setSkip(getRequestOffset());
15 | query.limit(getPageSize());
16 | query.orderByDescending("createdAt");
17 | return query.find();
18 | }
19 |
20 | public List getLatest() throws Exception {
21 | if (getData() == null || getData().size() == 0) {
22 | return null;
23 | }
24 | AVQuery query = new AVQuery<>("Entry");
25 | query.whereGreaterThan("createdAt", getData().get(0).getCreatedAt());
26 | return query.find();
27 | }
28 |
29 | @Override
30 | public List doInitialize() throws Exception {
31 | return getData();
32 | }
33 |
34 | @Override
35 | public List doRefresh() throws Exception {
36 | return getLatest();
37 | }
38 |
39 | @Override
40 | public List doMore() throws Exception {
41 | return getData();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
18 |
19 |
27 |
28 |
35 |
36 |
39 |
40 |
43 |
44 |
45 |
48 |
49 |
52 |
53 |
56 |
57 |
60 |
61 |
64 |
65 |
68 |
69 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/entry_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
22 |
23 |
30 |
31 |
41 |
42 |
51 |
52 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
24 |
25 |
33 |
34 |
43 |
44 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/recyclerview_footer.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
21 |
22 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/title.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/webview.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/app_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ganhuo/Android-Ganhuo/54cf48e8ad298b2b9c853c99831bdfc9c79f53bc/app/src/main/res/mipmap-hdpi/app_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/app_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ganhuo/Android-Ganhuo/54cf48e8ad298b2b9c853c99831bdfc9c79f53bc/app/src/main/res/mipmap-mdpi/app_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/app_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ganhuo/Android-Ganhuo/54cf48e8ad298b2b9c853c99831bdfc9c79f53bc/app/src/main/res/mipmap-xhdpi/app_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/app_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ganhuo/Android-Ganhuo/54cf48e8ad298b2b9c853c99831bdfc9c79f53bc/app/src/main/res/mipmap-xxhdpi/app_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/app_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ganhuo/Android-Ganhuo/54cf48e8ad298b2b9c853c99831bdfc9c79f53bc/app/src/main/res/mipmap-xxxhdpi/app_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 干货集中营
3 |
4 | Hello world!
5 | Settings
6 | 关于
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | reference
4 | reference
5 |
6 |
7 |
12 |
13 |
20 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
3 |
4 | buildscript {
5 | repositories {
6 | jcenter()
7 | maven {
8 | url "http://mvn.leancloud.cn/nexus/content/repositories/releases"
9 | }
10 | maven {
11 | url "https://oss.sonatype.org/content/repositories/snapshots/"
12 | }
13 | }
14 | dependencies {
15 | classpath 'com.android.tools.build:gradle:1.1.0'
16 |
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | jcenter()
25 | maven {
26 | url "http://mvn.leancloud.cn/nexus/content/repositories/releases"
27 | }
28 | maven {
29 | url "https://oss.sonatype.org/content/repositories/snapshots/"
30 | }
31 | maven {
32 | url "http://dl.bintray.com/fir/maven"
33 | }
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ganhuo/Android-Ganhuo/54cf48e8ad298b2b9c853c99831bdfc9c79f53bc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
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.2.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 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------