├── .gitattributes
├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── README.md
├── V2EX.iml
├── app
├── .gitignore
├── app-debug.apk
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── zzhoujay
│ │ └── v2ex
│ │ ├── MainActivity.java
│ │ ├── V2EX.java
│ │ ├── data
│ │ ├── DataManger.java
│ │ ├── DataProvider.java
│ │ ├── MemberProvider.java
│ │ ├── NodesProvider.java
│ │ ├── RepliesProvider.java
│ │ ├── TopicProvider.java
│ │ └── TopicsProvider.java
│ │ ├── interfaces
│ │ ├── ClickCallback.java
│ │ ├── MemberService.java
│ │ ├── NodeService.java
│ │ ├── NodesService.java
│ │ ├── Notifier.java
│ │ ├── OnItemClickListener.java
│ │ ├── OnLoadCompleteListener.java
│ │ ├── RepliesService.java
│ │ ├── TopicService.java
│ │ └── TopicsService.java
│ │ ├── model
│ │ ├── Member.java
│ │ ├── Node.java
│ │ ├── Replies.java
│ │ └── Topic.java
│ │ ├── net
│ │ ├── NetworkManager.java
│ │ ├── PersistentCookieStore.java
│ │ └── SerializableHttpCookie.java
│ │ ├── ui
│ │ ├── activity
│ │ │ ├── LoginActivity.java
│ │ │ ├── MemberActivity.java
│ │ │ ├── NewTopicActivity.java
│ │ │ ├── NodeActivity.java
│ │ │ ├── NodesActivity.java
│ │ │ └── TopicDetailActivity.java
│ │ ├── adapter
│ │ │ ├── NodesAdapter.java
│ │ │ ├── RepliesAdapter.java
│ │ │ └── TopicsAdapter.java
│ │ ├── dialog
│ │ │ └── ContentDialog.java
│ │ ├── fragment
│ │ │ ├── NodesFragment.java
│ │ │ ├── ReplyFragment.java
│ │ │ ├── TopicDetailFragment.java
│ │ │ └── TopicsFragment.java
│ │ └── view
│ │ │ └── SwipeToRefreshLayout.java
│ │ └── util
│ │ ├── ContentUtils.java
│ │ ├── FileComparator.java
│ │ ├── FileNameFilter.java
│ │ ├── FileUtils.java
│ │ ├── TimeUtils.java
│ │ └── UserUtils.java
│ └── res
│ ├── drawable-hdpi
│ ├── ic_add_white.png
│ ├── ic_dashboard_grey.png
│ ├── ic_done_white.png
│ ├── ic_drawer.png
│ ├── ic_exit_to_app_white.png
│ ├── ic_info_grey.png
│ ├── ic_refresh_white.png
│ ├── ic_send_grey.png
│ ├── ic_settings_grey.png
│ └── ic_view_agenda_grey.png
│ ├── drawable-mdpi
│ └── ic_drawer.png
│ ├── drawable-xhdpi
│ ├── ic_add_white.png
│ ├── ic_done_white.png
│ ├── ic_drawer.png
│ ├── ic_exit_to_app_white.png
│ ├── ic_refresh_white.png
│ └── ic_send_grey.png
│ ├── drawable-xxhdpi
│ └── ic_drawer.png
│ ├── drawable
│ ├── default_image.xml
│ └── image_btn_bg.xml
│ ├── layout
│ ├── activity_fragment.xml
│ ├── activity_fragment_floatactionbar.xml
│ ├── activity_login.xml
│ ├── activity_main.xml
│ ├── activity_member.xml
│ ├── activity_new_topic.xml
│ ├── drawer_header.xml
│ ├── fragment_content.xml
│ ├── fragment_recycler_view.xml
│ ├── fragment_reply.xml
│ ├── fragment_topic_detail.xml
│ ├── item_node.xml
│ ├── item_replies.xml
│ └── item_topic.xml
│ ├── menu
│ └── drawer.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-v21
│ └── styles.xml
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshot
├── Screenshot_2015-07-28-19-06-28.png
├── Screenshot_2015-07-28-19-06-34.png
├── Screenshot_2015-07-28-19-06-40.png
├── Screenshot_2015-07-28-19-07-02.png
├── Screenshot_2015-07-28-19-07-32.png
├── Screenshot_2015-07-28-19-07-43.png
├── Screenshot_2015-07-28-19-07-55.png
├── Screenshot_2015-07-28-19-29-23.png
├── Screenshot_2015-07-28-19-30-44.png
├── Screenshot_2015-07-28-19-37-09.png
└── Screenshot_2015-07-28-21-20-39.png
└── settings.gradle
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | V2EX
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Class structure
15 |
16 |
17 | Code maturity issues
18 |
19 |
20 | General
21 |
22 |
23 | Java language level migration aids
24 |
25 |
26 | Javadoc issues
27 |
28 |
29 | Performance issues
30 |
31 |
32 | TestNG
33 |
34 |
35 | Threading issues
36 |
37 |
38 |
39 |
40 | Abstraction issues
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # V2EX
2 |
3 | V2EX的非官方Android客户端,极力遵循Material Design风格
4 |
5 | ### 实现功能
6 | * 查看最新和最热话题
7 | * 查看所有节点
8 | * 查看节点里的话题
9 | * 查看话题详情、评论
10 | * 评论话题
11 | * 发表新话题
12 | * 登录
13 | * 查看用户信息
14 |
15 | > 依赖Glide、okhttp、retrofit
16 | > 有些地方参考了[v2ex-android](https://github.com/greatyao/v2ex-android)的实现
17 |
18 | ### 运行效果
19 | 
20 | 
21 | 
22 | 
23 | 
24 | 
25 | 
26 | 
27 | 
28 | 
29 |
30 | _by zzhoujay_
31 |
--------------------------------------------------------------------------------
/V2EX.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/app-debug.apk
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | //apply plugin: 'oneapm'
3 |
4 | android {
5 | compileSdkVersion 23
6 | buildToolsVersion "22.0.1"
7 |
8 | defaultConfig {
9 | applicationId "com.zzhoujay.v2ex"
10 | minSdkVersion 15
11 | targetSdkVersion 23
12 | versionCode 1
13 | versionName "1.0"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | repositories {
24 | maven {
25 | url "http://dl.bintray.com/zzhoujay/maven"
26 | }
27 | }
28 |
29 |
30 | dependencies {
31 | compile fileTree(include: ['*.jar'], dir: 'libs')
32 | // compile fileTree(dir:'E:\\Other SDK\\OneAPM_Android_Gradle_2.0.0\\agent',include:['*.jar'])
33 | compile 'com.android.support:appcompat-v7:23.4.0'
34 | compile 'com.android.support:design:23.4.0'
35 | compile 'com.android.support:cardview-v7:23.4.0'
36 | compile 'com.android.support:recyclerview-v7:23.4.0'
37 | compile 'com.squareup.retrofit:retrofit:1.9.0'
38 | compile 'com.squareup.okhttp:okhttp:2.4.0'
39 | compile 'com.zzhoujay.richtext:richtext:1.1.2'
40 | compile 'com.zzhoujay.advanceadapter:advanceadapter:1.0.2'
41 | // compile 'zhou.widget:advanceadapter:1.0'
42 | compile 'com.github.bumptech.glide:glide:3.7.0'
43 | }
44 |
--------------------------------------------------------------------------------
/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 E:\android_studio\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 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
49 |
53 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Parcelable;
6 | import android.support.design.widget.NavigationView;
7 | import android.support.design.widget.TabLayout;
8 | import android.support.v4.app.Fragment;
9 | import android.support.v4.app.FragmentPagerAdapter;
10 | import android.support.v4.view.GravityCompat;
11 | import android.support.v4.view.PagerAdapter;
12 | import android.support.v4.view.ViewPager;
13 | import android.support.v4.widget.DrawerLayout;
14 | import android.support.v7.app.ActionBar;
15 | import android.support.v7.app.AppCompatActivity;
16 | import android.support.v7.widget.Toolbar;
17 | import android.view.MenuItem;
18 | import android.view.View;
19 | import android.widget.ImageView;
20 | import android.widget.TextView;
21 |
22 | import com.bumptech.glide.Glide;
23 | import com.zzhoujay.v2ex.data.DataManger;
24 | import com.zzhoujay.v2ex.data.TopicsProvider;
25 | import com.zzhoujay.v2ex.interfaces.Notifier;
26 | import com.zzhoujay.v2ex.model.Member;
27 | import com.zzhoujay.v2ex.ui.activity.LoginActivity;
28 | import com.zzhoujay.v2ex.ui.activity.MemberActivity;
29 | import com.zzhoujay.v2ex.ui.activity.NodesActivity;
30 | import com.zzhoujay.v2ex.ui.dialog.ContentDialog;
31 | import com.zzhoujay.v2ex.ui.fragment.TopicsFragment;
32 |
33 |
34 | public class MainActivity extends AppCompatActivity {
35 |
36 | private TabLayout tabLayout;
37 | private ViewPager viewPager;
38 | private NavigationView navigationView;
39 | private TopicsFragment[] fragments;
40 | private ImageView icon;
41 | private TextView name, bio;
42 | private View header;
43 | private DrawerLayout drawerLayout;
44 | private ContentDialog about;
45 |
46 | private int[] ids = {R.string.tab1, R.string.tab2};
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | setContentView(R.layout.activity_main);
52 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
53 | setSupportActionBar(toolbar);
54 | ActionBar actionBar = getSupportActionBar();
55 | if (actionBar != null) {
56 | actionBar.setDisplayUseLogoEnabled(true);
57 | actionBar.setDisplayShowTitleEnabled(true);
58 | actionBar.setDisplayShowHomeEnabled(true);
59 | toolbar.setNavigationIcon(R.drawable.ic_drawer);
60 | toolbar.setNavigationOnClickListener(new View.OnClickListener() {
61 | @Override
62 | public void onClick(View v) {
63 | drawerLayout.openDrawer(GravityCompat.START);
64 | }
65 | });
66 | }
67 |
68 | fragments = new TopicsFragment[2];
69 | fragments[0] = TopicsFragment.newInstance(TopicsProvider.TopicType.LATEST);
70 | fragments[1] = TopicsFragment.newInstance(TopicsProvider.TopicType.HOT);
71 |
72 | initView();
73 | initData();
74 | initEvent();
75 |
76 | }
77 |
78 | private void initEvent() {
79 | navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
80 | @Override
81 | public boolean onNavigationItemSelected(MenuItem menuItem) {
82 | drawerLayout.closeDrawers();
83 | switch (menuItem.getItemId()) {
84 | case R.id.drawer_tab2:
85 | Intent intent = new Intent(MainActivity.this, NodesActivity.class);
86 | startActivity(intent);
87 | return true;
88 | case R.id.drawer_about:
89 | about = ContentDialog.newInstance(getString(R.string.about), getString(R.string.about_content));
90 | about.show(getSupportFragmentManager(), "about");
91 | return true;
92 | }
93 | return false;
94 | }
95 | });
96 | }
97 |
98 | private void initData() {
99 | tabLayout.addTab(tabLayout.newTab().setText(ids[0]));
100 | tabLayout.addTab(tabLayout.newTab().setText(ids[1]));
101 |
102 | PagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
103 | @Override
104 | public Fragment getItem(int position) {
105 | return fragments[position];
106 | }
107 |
108 | @Override
109 | public int getCount() {
110 | return fragments.length;
111 | }
112 |
113 | @Override
114 | public CharSequence getPageTitle(int position) {
115 | return getString(ids[position]);
116 | }
117 | };
118 |
119 | viewPager.setAdapter(adapter);
120 | tabLayout.setupWithViewPager(viewPager);
121 | tabLayout.setTabsFromPagerAdapter(adapter);
122 |
123 | }
124 |
125 | private void initView() {
126 | drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
127 | navigationView = (NavigationView) findViewById(R.id.navigationView);
128 | tabLayout = (TabLayout) findViewById(R.id.tabLayout);
129 | viewPager = (ViewPager) findViewById(R.id.viewPage);
130 |
131 | header=navigationView.inflateHeaderView(R.layout.drawer_header);
132 |
133 | icon = (ImageView) header.findViewById(R.id.header_icon);
134 | name = (TextView) header.findViewById(R.id.header_name);
135 | bio = (TextView) header.findViewById(R.id.header_bio);
136 |
137 | }
138 |
139 | private void setUserInfo(Member member) {
140 | if (member == null) {
141 | name.setText(R.string.login);
142 | icon.setImageResource(R.mipmap.ic_launcher);
143 | bio.setText("");
144 | return;
145 | }
146 | name.setText(member.username);
147 | bio.setText(member.bio);
148 | Glide.with(this)
149 | .load("http:" + member.avatar_large)
150 | .placeholder(R.mipmap.ic_launcher)
151 | .error(R.mipmap.ic_launcher)
152 | .centerCrop()
153 | .crossFade()
154 | .into(icon);
155 | }
156 |
157 | @Override
158 | protected void onResume() {
159 | super.onResume();
160 | Member user = V2EX.getInstance().getSelf();
161 | if (user != null) {
162 | setUserInfo(user);
163 | name.setOnClickListener(userInfoListener);
164 | header.setOnClickListener(userInfoListener);
165 | icon.setOnClickListener(userInfoListener);
166 | } else {
167 | setUserInfo(null);
168 | icon.setOnClickListener(loginListener);
169 | name.setOnClickListener(loginListener);
170 | }
171 | }
172 |
173 | private View.OnClickListener loginListener = new View.OnClickListener() {
174 | @Override
175 | public void onClick(View v) {
176 | Intent intent = new Intent(MainActivity.this, LoginActivity.class);
177 | startActivity(intent);
178 | }
179 | };
180 |
181 | private View.OnClickListener userInfoListener = new View.OnClickListener() {
182 | @Override
183 | public void onClick(View v) {
184 | Intent intent = new Intent(MainActivity.this, MemberActivity.class);
185 | intent.putExtra(Member.MEMBER, (Parcelable) V2EX.getInstance().getSelf());
186 | startActivity(intent);
187 | }
188 | };
189 |
190 | private Notifier notifier = new Notifier() {
191 | @Override
192 | public void notice() {
193 | Member user = V2EX.getInstance().getSelf();
194 | if (user != null) {
195 | setUserInfo(user);
196 | name.setOnClickListener(null);
197 | header.setOnClickListener(userInfoListener);
198 | } else {
199 | name.setOnClickListener(loginListener);
200 | }
201 | }
202 | };
203 |
204 | @Override
205 | protected void onDestroy() {
206 | super.onDestroy();
207 | DataManger.getInstance().removeProvider(TopicsProvider.TopicType.FILE_NAME_HOT);
208 | DataManger.getInstance().removeProvider(TopicsProvider.TopicType.FILE_NAME_LATEST);
209 | V2EX.getInstance().removeSelfChangeNotifier(notifier);
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/V2EX.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex;
2 |
3 | import android.app.Application;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 | import android.widget.Toast;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import com.zzhoujay.v2ex.data.DataManger;
12 | import com.zzhoujay.v2ex.data.MemberProvider;
13 | import com.zzhoujay.v2ex.interfaces.Notifier;
14 | import com.zzhoujay.v2ex.model.Member;
15 |
16 | /**
17 | * Created by 州 on 2015/7/18 0018.
18 | * Application
19 | */
20 | public class V2EX extends Application {
21 |
22 | public static final String SINGIN_URL = "http://v2ex.com/signin";
23 | public static final String SITE_URL = "http://v2ex.com";
24 |
25 |
26 | private static V2EX v2EX;
27 |
28 | private Member self;
29 | private List selfStateChangeNotifier;
30 |
31 | @Override
32 | public void onCreate() {
33 | super.onCreate();
34 | v2EX = this;
35 | selfStateChangeNotifier = new ArrayList<>();
36 | setSelf(MemberProvider.getSelf());
37 | if (self != null) {
38 | MemberProvider memberProvider = new MemberProvider(DataManger.getInstance().getRestAdapter(), self.username, true);
39 | DataManger.getInstance().addProvider(memberProvider.FILE_NAME, memberProvider);
40 | DataManger.getInstance().refresh(memberProvider.FILE_NAME, null);
41 | }
42 | }
43 |
44 | public static V2EX getInstance() {
45 | return v2EX;
46 | }
47 |
48 | public boolean isNetworkConnected() {
49 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
50 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
51 | return networkInfo != null && networkInfo.isAvailable();
52 | }
53 |
54 | public void toast(String msg) {
55 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
56 | }
57 |
58 | public void toast(int res) {
59 | Toast.makeText(this, res, Toast.LENGTH_SHORT).show();
60 | }
61 |
62 |
63 | public boolean saveCache() {
64 | return false;
65 | }
66 |
67 | public boolean isSelf(String username) {
68 | return self != null && self.username.equals(username);
69 | }
70 |
71 | public void setSelf(Member member) {
72 | this.self = member;
73 | for (Notifier notifier : selfStateChangeNotifier) {
74 | notifier.notice();
75 | }
76 | }
77 |
78 | public Member getSelf() {
79 | return self;
80 | }
81 |
82 | public boolean isLogin() {
83 | return self != null;
84 | }
85 |
86 | public boolean logout() {
87 | setSelf(null);
88 | for (Notifier notifier : selfStateChangeNotifier) {
89 | notifier.notice();
90 | }
91 | return MemberProvider.clearSelf();
92 | }
93 |
94 | @SuppressWarnings("unused")
95 | public void addSelfChangeNotifier(Notifier notifier) {
96 | selfStateChangeNotifier.add(notifier);
97 | }
98 |
99 | public void removeSelfChangeNotifier(Notifier notifier) {
100 | selfStateChangeNotifier.remove(notifier);
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/DataManger.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import android.support.annotation.Nullable;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import retrofit.RestAdapter;
9 | import com.zzhoujay.v2ex.R;
10 | import com.zzhoujay.v2ex.V2EX;
11 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
12 |
13 | /**
14 | * Created by 州 on 2015/7/20 0020.
15 | * 数据管理器(内存、本地缓存、联网加载)
16 | */
17 | public class DataManger {
18 |
19 | private Map providerMap;
20 | private RestAdapter restAdapter;
21 |
22 | private DataManger() {
23 | providerMap = new HashMap<>();
24 | restAdapter = new RestAdapter.Builder().setEndpoint("http://v2ex.com").build();
25 | }
26 |
27 | private static DataManger dataManger;
28 |
29 | public static DataManger getInstance() {
30 | if (dataManger == null) {
31 | dataManger = new DataManger();
32 | }
33 | return dataManger;
34 | }
35 |
36 | /**
37 | * 联网刷新数据
38 | *
39 | * @param key key
40 | * @param onLoadComplete 回调
41 | * @param type
42 | */
43 | @SuppressWarnings("unchecked")
44 | public void refresh(String key, @Nullable final OnLoadCompleteListener onLoadComplete) {
45 | final DataProvider provider = providerMap.get(key);
46 | if (provider != null) {
47 | //进行联网加载
48 | provider.getFromNet(new OnLoadCompleteListener() {
49 | @Override
50 | public void loadComplete(T o) {
51 | if (o != null) {
52 | //联网加载成功
53 | provider.set(o);
54 | if (provider.needCache())
55 | provider.persistence();
56 | } else {
57 | //联网加载失败
58 | V2EX.getInstance().toast(R.string.load_error);
59 | }
60 | if (onLoadComplete != null) {
61 | onLoadComplete.loadComplete(o);
62 | }
63 | }
64 | });
65 | } else {
66 | if (onLoadComplete != null) {
67 | onLoadComplete.loadComplete(null);
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * 按照 内存->本地缓存->网络数据 的顺序加载数据
74 | *
75 | * @param key key
76 | * @param onLoadComplete 回调
77 | * @param type
78 | */
79 | @SuppressWarnings("unchecked")
80 | public void getData(String key, final OnLoadCompleteListener onLoadComplete) {
81 | final DataProvider provider = providerMap.get(key);
82 | if (provider == null) {
83 | if (onLoadComplete != null) {
84 | onLoadComplete.loadComplete(null);
85 | }
86 | return;
87 | }
88 | if (provider.hasLoad()) {
89 | //已加载至内存
90 | if (onLoadComplete != null) {
91 | onLoadComplete.loadComplete(provider.get());
92 | }
93 | } else {
94 | //未加载到内存
95 | provider.getFromLocal(new OnLoadCompleteListener() {
96 | @Override
97 | public void loadComplete(T o) {
98 | if (o != null) {
99 | //从本地加载到了
100 | provider.set(o);
101 | if (onLoadComplete != null) {
102 | onLoadComplete.loadComplete(o);
103 | }
104 | } else {
105 | //本地没有缓存,联网加载
106 | provider.getFromNet(new OnLoadCompleteListener() {
107 | @Override
108 | public void loadComplete(T o) {
109 | if (o != null) {
110 | //联网加载成功
111 | provider.set(o);
112 | if (provider.needCache())
113 | provider.persistence();
114 | } else {
115 | //联网加载失败
116 | V2EX.getInstance().toast(R.string.load_error);
117 | }
118 | if (onLoadComplete != null) {
119 | onLoadComplete.loadComplete(o);
120 | }
121 | }
122 | });
123 | }
124 | }
125 | });
126 | }
127 | }
128 |
129 | /**
130 | * 添加数据提供器
131 | *
132 | * @param key key
133 | * @param provider 数据提供器
134 | * @param type
135 | */
136 | public void addProvider(String key, DataProvider provider) {
137 | if (provider != null && key != null)
138 | if (!providerMap.containsKey(key)) {
139 | providerMap.put(key, provider);
140 | }
141 | }
142 |
143 | /**
144 | * 添加数据提供器
145 | *
146 | * @param key key
147 | * @param provider 数据提供器
148 | * @param flag 是否强制添加
149 | * @param type
150 | */
151 | public void addProvider(String key, DataProvider provider, boolean flag) {
152 | if (provider != null && key != null) {
153 | if (flag) {
154 | providerMap.put(key, provider);
155 | } else {
156 | if (!providerMap.containsKey(key)) {
157 | providerMap.put(key, provider);
158 | }
159 | }
160 | }
161 | }
162 |
163 | /**
164 | * 是否已经添加
165 | *
166 | * @param key key
167 | * @return boolean
168 | */
169 | @SuppressWarnings("unused")
170 | public boolean hasProvider(String key) {
171 | return providerMap.containsKey(key);
172 | }
173 |
174 | public RestAdapter getRestAdapter() {
175 | return restAdapter;
176 | }
177 |
178 | public void removeProvider(String key) {
179 | providerMap.remove(key);
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/DataProvider.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
4 |
5 | /**
6 | * Created by 州 on 2015/7/19 0019.
7 | * 数据提供器接口
8 | */
9 | public interface DataProvider {
10 |
11 | /**
12 | * 数据持久化
13 | */
14 | void persistence();
15 |
16 | /**
17 | * 获取数据
18 | *
19 | * @return 数据
20 | */
21 | T get();
22 |
23 | /**
24 | * 设置数据
25 | *
26 | * @param t 数据
27 | */
28 | void set(T t);
29 |
30 | /**
31 | * 从本地获取数据
32 | *
33 | * @param loadComplete 回调
34 | */
35 | void getFromLocal(OnLoadCompleteListener loadComplete);
36 |
37 | /**
38 | * 从网络加载数据
39 | *
40 | * @param loadComplete 回调
41 | */
42 | void getFromNet(OnLoadCompleteListener loadComplete);
43 |
44 | /**
45 | * 是否已经加载
46 | *
47 | * @return boolean
48 | */
49 | boolean hasLoad();
50 |
51 | /**
52 | * 是否需要缓存
53 | *
54 | * @return boolean
55 | */
56 | boolean needCache();
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/MemberProvider.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 |
7 | import retrofit.Callback;
8 | import retrofit.RestAdapter;
9 | import retrofit.RetrofitError;
10 | import retrofit.client.Response;
11 | import com.zzhoujay.v2ex.R;
12 | import com.zzhoujay.v2ex.V2EX;
13 | import com.zzhoujay.v2ex.interfaces.MemberService;
14 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
15 | import com.zzhoujay.v2ex.model.Member;
16 | import com.zzhoujay.v2ex.util.FileUtils;
17 |
18 | /**
19 | * Created by zzhoujay on 2015/7/22 0022.
20 | * Member的数据提供者的实现
21 | */
22 | public class MemberProvider implements DataProvider {
23 |
24 | public static final String SElF = "self.cache";
25 |
26 | public String FILE_NAME = "member_";
27 |
28 | private Member member;
29 | private MemberService memberService;
30 | private boolean isSelf;
31 | private String username;
32 |
33 | public MemberProvider(RestAdapter restAdapter, String username, boolean self) {
34 | this.isSelf = self;
35 | this.username = username;
36 | memberService = restAdapter.create(MemberService.class);
37 | FILE_NAME = isSelf ? SElF : (FILE_NAME + username);
38 | }
39 |
40 | @Override
41 | public void persistence() {
42 | if (hasLoad()) {
43 | new Thread() {
44 | @Override
45 | public void run() {
46 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
47 | FileUtils.writeObject(file, member);
48 | }
49 | }.start();
50 | }
51 | }
52 |
53 | @Override
54 | public Member get() {
55 | return member;
56 | }
57 |
58 | @Override
59 | public void set(Member member) {
60 | this.member = member;
61 | }
62 |
63 | @Override
64 | public void getFromLocal(OnLoadCompleteListener loadComplete) {
65 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
66 | Member m = null;
67 | if (file.exists()) {
68 | try {
69 | m = (Member) FileUtils.readObject(file);
70 | } catch (Exception e) {
71 | Log.d("getFromLocal", "error", e);
72 | }
73 | }
74 | if (loadComplete != null) {
75 | loadComplete.loadComplete(m);
76 | }
77 | }
78 |
79 | @Override
80 | public void getFromNet(final OnLoadCompleteListener loadComplete) {
81 | if (!V2EX.getInstance().isNetworkConnected()) {
82 | //网络未连接
83 | V2EX.getInstance().toast(R.string.network_error);
84 | if (loadComplete != null) {
85 | loadComplete.loadComplete(null);
86 | }
87 | return;
88 | }
89 | memberService.getMember(username, new Callback() {
90 | @Override
91 | public void success(Member member, Response response) {
92 | Log.d("getFromNet", "success");
93 | if (loadComplete != null) {
94 | loadComplete.loadComplete(member);
95 | }
96 | }
97 |
98 | @Override
99 | public void failure(RetrofitError error) {
100 | Log.d("getFromNet", "failure", error);
101 | if (loadComplete != null) {
102 | loadComplete.loadComplete(null);
103 | }
104 | }
105 | });
106 | }
107 |
108 | @Override
109 | public boolean hasLoad() {
110 | return member != null;
111 | }
112 |
113 | @Override
114 | public boolean needCache() {
115 | return isSelf || V2EX.getInstance().saveCache();
116 | }
117 |
118 | public static Member getSelf() {
119 | File file = new File(V2EX.getInstance().getCacheDir(), SElF);
120 | Member self = null;
121 | if (file.exists()) {
122 | try {
123 | self = (Member) FileUtils.readObject(file);
124 | } catch (Exception e) {
125 | Log.d("getSelf", "error", e);
126 | }
127 | }
128 | return self;
129 | }
130 |
131 | public static boolean clearSelf() {
132 | File file = new File(V2EX.getInstance().getCacheDir(), SElF);
133 | return file.exists() && file.delete();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/NodesProvider.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 | import java.util.List;
7 |
8 | import retrofit.Callback;
9 | import retrofit.RestAdapter;
10 | import retrofit.RetrofitError;
11 | import retrofit.client.Response;
12 | import com.zzhoujay.v2ex.R;
13 | import com.zzhoujay.v2ex.V2EX;
14 | import com.zzhoujay.v2ex.interfaces.NodesService;
15 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
16 | import com.zzhoujay.v2ex.model.Node;
17 | import com.zzhoujay.v2ex.util.FileUtils;
18 |
19 | /**
20 | * Created by 州 on 2015/7/20 0020.
21 | * Node的数据提供器的实现
22 | */
23 | public class NodesProvider implements DataProvider> {
24 |
25 | public static final String FILE_NAME = "node.cache";
26 |
27 | private List nodes;
28 | private NodesService nodesService;
29 |
30 | public NodesProvider(RestAdapter restAdapter) {
31 | nodesService = restAdapter.create(NodesService.class);
32 | }
33 |
34 | @Override
35 | public void persistence() {
36 | if (nodes == null) {
37 | return;
38 | }
39 | new Thread() {
40 | @Override
41 | public void run() {
42 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
43 | FileUtils.writeObject(file, nodes);
44 | }
45 | }.start();
46 | }
47 |
48 | @Override
49 | public List get() {
50 | return nodes;
51 | }
52 |
53 | @Override
54 | public void set(List nodes) {
55 | this.nodes = nodes;
56 | }
57 |
58 | @Override
59 | @SuppressWarnings("unchecked")
60 | public void getFromLocal(OnLoadCompleteListener> loadComplete) {
61 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
62 | List ns = null;
63 | if (file.exists()) {
64 | try {
65 | ns = (List) FileUtils.readObject(file);
66 | } catch (Exception e) {
67 | Log.d("getFromLocal", "NodesProvider", e);
68 | }
69 | }
70 | if (loadComplete != null) {
71 | loadComplete.loadComplete(ns);
72 | }
73 | }
74 |
75 | @Override
76 | public void getFromNet(final OnLoadCompleteListener> loadComplete) {
77 | if (!V2EX.getInstance().isNetworkConnected()) {
78 | //网络未连接
79 | V2EX.getInstance().toast(R.string.network_error);
80 | if (loadComplete != null) {
81 | loadComplete.loadComplete(null);
82 | }
83 | return;
84 | }
85 | nodesService.listNode(new Callback>() {
86 | @Override
87 | public void success(List nodes, Response response) {
88 | Log.d("getFromNet", "success");
89 | if (loadComplete != null) {
90 | loadComplete.loadComplete(nodes);
91 | }
92 | }
93 |
94 | @Override
95 | public void failure(RetrofitError error) {
96 | Log.d("getFromNet", "failure", error);
97 | if (loadComplete != null) {
98 | loadComplete.loadComplete(null);
99 | }
100 | }
101 | });
102 | }
103 |
104 | @Override
105 | public boolean hasLoad() {
106 | return nodes != null;
107 | }
108 |
109 | @Override
110 | public boolean needCache() {
111 | return true;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/RepliesProvider.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 | import java.util.List;
7 |
8 | import retrofit.Callback;
9 | import retrofit.RestAdapter;
10 | import retrofit.RetrofitError;
11 | import retrofit.client.Response;
12 | import com.zzhoujay.v2ex.R;
13 | import com.zzhoujay.v2ex.V2EX;
14 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
15 | import com.zzhoujay.v2ex.interfaces.RepliesService;
16 | import com.zzhoujay.v2ex.model.Replies;
17 | import com.zzhoujay.v2ex.model.Topic;
18 | import com.zzhoujay.v2ex.util.FileUtils;
19 |
20 | /**
21 | * Created by 州 on 2015/7/20 0020.
22 | * Replies的数据提供器的实现
23 | */
24 | public class RepliesProvider implements DataProvider> {
25 |
26 | public String FILE_NAME = "topic_replies_";
27 |
28 | private List replies;
29 | private Topic topic;
30 | private RepliesService repliesService;
31 |
32 | public RepliesProvider(RestAdapter restAdapter, Topic topic) {
33 | this.topic = topic;
34 | FILE_NAME = FILE_NAME + topic.id;
35 | repliesService = restAdapter.create(RepliesService.class);
36 | }
37 |
38 | @Override
39 | public void persistence() {
40 | if(replies==null){
41 | return;
42 | }
43 | new Thread() {
44 | @Override
45 | public void run() {
46 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
47 | FileUtils.writeObject(file, replies);
48 | }
49 | }.start();
50 | }
51 |
52 | @Override
53 | public List get() {
54 | return replies;
55 | }
56 |
57 | @Override
58 | public void set(List replies) {
59 | this.replies = replies;
60 | }
61 |
62 | @Override
63 | @SuppressWarnings("unchecked")
64 | public void getFromLocal(OnLoadCompleteListener> loadComplete) {
65 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
66 | List rs = null;
67 | if (file.exists()) {
68 | try {
69 | rs = (List) FileUtils.readObject(file);
70 | } catch (Exception e) {
71 | Log.d("getFromLocal", "RepliesProvider", e);
72 | }
73 | }
74 | if (loadComplete != null) {
75 | loadComplete.loadComplete(rs);
76 | }
77 | }
78 |
79 | @Override
80 | public void getFromNet(final OnLoadCompleteListener> loadComplete) {
81 | if (!V2EX.getInstance().isNetworkConnected()) {
82 | //网络未连接
83 | V2EX.getInstance().toast(R.string.network_error);
84 | if (loadComplete != null) {
85 | loadComplete.loadComplete(null);
86 | }
87 | return;
88 | }
89 | repliesService.getReplise(topic.id, new Callback>() {
90 | @Override
91 | public void success(List replies, Response response) {
92 | Log.d("getFromNet", "success");
93 | if (loadComplete != null) {
94 | loadComplete.loadComplete(replies);
95 | }
96 | }
97 |
98 | @Override
99 | public void failure(RetrofitError error) {
100 | Log.d("getFromNet", "failure", error);
101 | if (loadComplete != null) {
102 | loadComplete.loadComplete(null);
103 | }
104 | }
105 | });
106 | }
107 |
108 | @Override
109 | public boolean hasLoad() {
110 | return replies != null;
111 | }
112 |
113 | @Override
114 | public boolean needCache() {
115 | return V2EX.getInstance().saveCache();
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/TopicProvider.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 | import java.util.List;
7 |
8 | import retrofit.Callback;
9 | import retrofit.RestAdapter;
10 | import retrofit.RetrofitError;
11 | import retrofit.client.Response;
12 | import com.zzhoujay.v2ex.R;
13 | import com.zzhoujay.v2ex.V2EX;
14 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
15 | import com.zzhoujay.v2ex.interfaces.TopicService;
16 | import com.zzhoujay.v2ex.model.Topic;
17 | import com.zzhoujay.v2ex.util.FileUtils;
18 |
19 | /**
20 | * Created by zzhoujay on 2015/7/22 0022.
21 | * Topic的数据提供器的实现
22 | */
23 | public class TopicProvider implements DataProvider {
24 |
25 | public String FILE_NAME = "topic_";
26 |
27 | private Topic topic;
28 | private TopicService topicService;
29 | private int id;
30 |
31 | public TopicProvider(RestAdapter restAdapter, int id) {
32 | this.id = id;
33 | topicService = restAdapter.create(TopicService.class);
34 | FILE_NAME += id;
35 | }
36 |
37 |
38 | @Override
39 | public void persistence() {
40 | if (!hasLoad()) {
41 | return;
42 | }
43 | new Thread() {
44 | @Override
45 | public void run() {
46 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
47 | FileUtils.writeObject(file, topic);
48 | }
49 | }.start();
50 | }
51 |
52 | @Override
53 | public Topic get() {
54 | return topic;
55 | }
56 |
57 | @Override
58 | public void set(Topic topic) {
59 | this.topic = topic;
60 | }
61 |
62 | @Override
63 | public void getFromLocal(OnLoadCompleteListener loadComplete) {
64 | File file = new File(V2EX.getInstance().getCacheDir(), FILE_NAME);
65 | Topic t = null;
66 | if (file.exists()) {
67 | try {
68 | t = (Topic) FileUtils.readObject(file);
69 | } catch (Exception e) {
70 | Log.d("getFromLocal", "error", e);
71 | }
72 | }
73 | if (loadComplete != null) {
74 | loadComplete.loadComplete(t);
75 | }
76 | }
77 |
78 | @Override
79 | public void getFromNet(final OnLoadCompleteListener loadComplete) {
80 | if (!V2EX.getInstance().isNetworkConnected()) {
81 | //网络未连接
82 | V2EX.getInstance().toast(R.string.network_error);
83 | if (loadComplete != null) {
84 | loadComplete.loadComplete(null);
85 | }
86 | return;
87 | }
88 | topicService.getTopic(id, new Callback>() {
89 | @Override
90 | public void success(List topic, Response response) {
91 | Log.d("getFromNet", "success_topic");
92 | if (loadComplete != null) {
93 | loadComplete.loadComplete(topic.get(0));
94 | }
95 | }
96 |
97 | @Override
98 | public void failure(RetrofitError error) {
99 | Log.d("getFromNet", "failure_topic", error);
100 | if (loadComplete != null) {
101 | loadComplete.loadComplete(null);
102 | }
103 | }
104 | });
105 | }
106 |
107 | @Override
108 | public boolean hasLoad() {
109 | return topic != null;
110 | }
111 |
112 | @Override
113 | public boolean needCache() {
114 | return V2EX.getInstance().saveCache();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/data/TopicsProvider.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.data;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.util.Log;
6 |
7 | import java.io.File;
8 | import java.io.Serializable;
9 | import java.util.List;
10 |
11 | import retrofit.Callback;
12 | import retrofit.RestAdapter;
13 | import retrofit.RetrofitError;
14 | import retrofit.client.Response;
15 | import com.zzhoujay.v2ex.R;
16 | import com.zzhoujay.v2ex.V2EX;
17 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
18 | import com.zzhoujay.v2ex.interfaces.TopicsService;
19 | import com.zzhoujay.v2ex.model.Topic;
20 | import com.zzhoujay.v2ex.util.FileUtils;
21 |
22 | /**
23 | * Created by 州 on 2015/7/20 0020.
24 | * Topic列表的数据提供器的实现
25 | */
26 | public class TopicsProvider implements DataProvider> {
27 |
28 | private TopicType topicType;
29 |
30 | private List topics;
31 | private TopicsService topicsService;
32 |
33 | public TopicsProvider(RestAdapter restAdapter, TopicType topicType) {
34 | topicsService = restAdapter.create(TopicsService.class);
35 | this.topicType = topicType;
36 | }
37 |
38 | @Override
39 | public void persistence() {
40 | if (topics == null) {
41 | return;
42 | }
43 | new Thread() {
44 | @Override
45 | public void run() {
46 | File file = new File(V2EX.getInstance().getCacheDir(), topicType.fileName);
47 | FileUtils.writeObject(file, topics);
48 | }
49 | }.start();
50 | }
51 |
52 | @Override
53 | public List get() {
54 | return topics;
55 | }
56 |
57 | @Override
58 | public void set(List topics) {
59 | this.topics = topics;
60 | }
61 |
62 | @Override
63 | @SuppressWarnings("unchecked")
64 | public void getFromLocal(OnLoadCompleteListener> loadComplete) {
65 | File file = new File(V2EX.getInstance().getCacheDir(), topicType.fileName);
66 | List ts = null;
67 | if (file.exists()) {
68 | try {
69 | ts = (List) FileUtils.readObject(file);
70 | } catch (Exception e) {
71 | Log.d("getFromLocal", "TopicsProvider", e);
72 | }
73 | }
74 | if (loadComplete != null) {
75 | loadComplete.loadComplete(ts);
76 | }
77 | }
78 |
79 |
80 | @Override
81 | public void getFromNet(final OnLoadCompleteListener> loadComplete) {
82 | if (!V2EX.getInstance().isNetworkConnected()) {
83 | //网络未连接
84 | V2EX.getInstance().toast(R.string.network_error);
85 | if (loadComplete != null) {
86 | loadComplete.loadComplete(null);
87 | }
88 | return;
89 | }
90 | //加载完成后的回调
91 | Callback> callback = new Callback>() {
92 | @Override
93 | public void success(List topics, Response response) {
94 | Log.d("getFromNet", "success");
95 | if (loadComplete != null) {
96 | loadComplete.loadComplete(topics);
97 | }
98 | }
99 |
100 | @Override
101 | public void failure(RetrofitError error) {
102 | Log.d("getFromNet", "failure", error);
103 | if (loadComplete != null) {
104 | loadComplete.loadComplete(null);
105 | }
106 | }
107 | };
108 |
109 | if (TopicType.FILE_NAME_LATEST.equals(topicType.fileName)) {//最新主题
110 | topicsService.getLatest(callback);
111 | } else if (TopicType.FILE_NAME_HOT.equals(topicType.fileName)) {//最热主题
112 | topicsService.getHot(callback);
113 | } else {//其他主题
114 | if (topicType.nodeName != null) {//通过节点名字查找
115 | topicsService.getTopicsByNodeName(topicType.nodeName, callback);
116 | } else if (topicType.userName != null) {//通过用户名查找
117 | topicsService.getTopicsByUserName(topicType.userName, callback);
118 | } else if (topicType.nodeId != -1) {//通过节点ID查找
119 | topicsService.getTopicByNodeId(topicType.nodeId, callback);
120 | } else {//异常情况
121 | V2EX.getInstance().toast(R.string.unknown_error);
122 | if (loadComplete != null) {
123 | loadComplete.loadComplete(null);
124 | }
125 | }
126 | }
127 | }
128 |
129 |
130 | @Override
131 | public boolean hasLoad() {
132 | return topics != null;
133 | }
134 |
135 | @Override
136 | public boolean needCache() {
137 | return topicType == TopicType.HOT || topicType == TopicType.LATEST;
138 | }
139 |
140 | public static class TopicType implements Serializable, Parcelable {
141 |
142 | public static final String FILE_NAME_LATEST = "latest.cache";
143 | public static final String FILE_NAME_HOT = "hot.cache";
144 |
145 | public String fileName;
146 | public String userName;
147 | public String nodeName;
148 | public int nodeId;
149 |
150 | public static final TopicType LATEST = new TopicType(FILE_NAME_LATEST);
151 | public static final TopicType HOT = new TopicType(FILE_NAME_HOT);
152 |
153 | private TopicType(String fileName) {
154 | this.fileName = fileName;
155 | userName = null;
156 | nodeName = null;
157 | nodeId = -1;
158 | }
159 |
160 | public static TopicType newTopicTypeByUserName(String fileName, String userName) {
161 | TopicType topicType = new TopicType(fileName);
162 | topicType.userName = userName;
163 | return topicType;
164 | }
165 |
166 | @SuppressWarnings("unused")
167 | public static TopicType newTopicTypeByNodeName(String fileName, String nodeName) {
168 | TopicType topicType = new TopicType(fileName);
169 | topicType.nodeName = nodeName;
170 | return topicType;
171 | }
172 |
173 | public static TopicType newTopicTypeByNodeId(String fileName, int nodeId) {
174 | TopicType topicType = new TopicType(fileName);
175 | topicType.nodeId = nodeId;
176 | return topicType;
177 | }
178 |
179 |
180 | @Override
181 | public int describeContents() {
182 | return 0;
183 | }
184 |
185 | @Override
186 | public void writeToParcel(Parcel dest, int flags) {
187 | dest.writeString(this.fileName);
188 | dest.writeString(this.userName);
189 | dest.writeString(this.nodeName);
190 | dest.writeInt(this.nodeId);
191 | }
192 |
193 | protected TopicType(Parcel in) {
194 | this.fileName = in.readString();
195 | this.userName = in.readString();
196 | this.nodeName = in.readString();
197 | this.nodeId = in.readInt();
198 | }
199 |
200 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
201 | public TopicType createFromParcel(Parcel source) {
202 | return new TopicType(source);
203 | }
204 |
205 | public TopicType[] newArray(int size) {
206 | return new TopicType[size];
207 | }
208 | };
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/ClickCallback.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | /**
4 | * Created by 州 on 2015/7/20 0020.
5 | * 点击回调
6 | */
7 | public interface ClickCallback {
8 | void callback(T t);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/MemberService.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import retrofit.Callback;
4 | import retrofit.http.GET;
5 | import retrofit.http.Query;
6 | import com.zzhoujay.v2ex.model.Member;
7 |
8 | /**
9 | * Created by 州 on 2015/7/18 0018.
10 | * Member的Service
11 | */
12 | public interface MemberService {
13 | @GET("/api/members/show.json")
14 | void getMember(@Query("username") String username, Callback memberCallback);
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/NodeService.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import retrofit.Callback;
4 | import retrofit.http.GET;
5 | import retrofit.http.Query;
6 | import com.zzhoujay.v2ex.model.Node;
7 |
8 | /**
9 | * Created by 州 on 2015/7/20 0020.
10 | * Node的Service
11 | */
12 | @SuppressWarnings("unused")
13 | public interface NodeService {
14 |
15 | @GET("/api/nodes/show.json")
16 | @SuppressWarnings("unused")
17 | void getNode(@Query("id") int id, Callback node);
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/NodesService.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import java.util.List;
4 |
5 | import retrofit.Callback;
6 | import retrofit.http.GET;
7 | import com.zzhoujay.v2ex.model.Node;
8 |
9 | /**
10 | * Created by 州 on 2015/7/18 0018.
11 | * Node列表的Service
12 | */
13 | public interface NodesService {
14 | @GET("/api/nodes/all.json")
15 | void listNode(Callback> ns);
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/Notifier.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | /**
4 | * Created by zzhoujay on 2015/7/28 0028.
5 | * 消息通知
6 | */
7 | public interface Notifier {
8 | void notice();
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/OnItemClickListener.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Created by 州 on 2015/7/20 0020.
7 | * 列表项的item点击回调接口
8 | */
9 | public interface OnItemClickListener {
10 | void onItemClicked(View view, int position);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/OnLoadCompleteListener.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | /**
4 | * Created by zzhoujay on 2015/7/24 0024.
5 | * 加载完成后的回调接口
6 | */
7 | public interface OnLoadCompleteListener {
8 | void loadComplete(T t);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/RepliesService.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import java.util.List;
4 |
5 | import retrofit.Callback;
6 | import retrofit.http.GET;
7 | import retrofit.http.Query;
8 | import com.zzhoujay.v2ex.model.Replies;
9 |
10 | /**
11 | * Created by 州 on 2015/7/18 0018.
12 | * Replies的Service
13 | */
14 | public interface RepliesService {
15 | @GET("/api/replies/show.json")
16 | void getReplise(@Query("topic_id") int topic_id, Callback> callback);
17 |
18 | @SuppressWarnings("unused")
19 | @GET("/api/replies/show.json")
20 | void getReplise(@Query("topic_id") int topic_id, @Query("page") int page, @Query("page_size") int page_size, Callback> callback);
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/TopicService.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import java.util.List;
4 |
5 | import retrofit.Callback;
6 | import retrofit.http.GET;
7 | import retrofit.http.Query;
8 | import com.zzhoujay.v2ex.model.Topic;
9 |
10 | /**
11 | * Created by 州 on 2015/7/20 0020.
12 | * Topic的Service
13 | */
14 | public interface TopicService {
15 |
16 | @GET("/api/topics/show.json")
17 | void getTopic(@Query("id") int id, Callback> topicCallback);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/interfaces/TopicsService.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.interfaces;
2 |
3 | import java.util.List;
4 |
5 | import retrofit.Callback;
6 | import retrofit.http.GET;
7 | import retrofit.http.Query;
8 | import com.zzhoujay.v2ex.model.Topic;
9 |
10 | /**
11 | * Created by 州 on 2015/7/18 0018.
12 | * Topic列表的Service
13 | */
14 | public interface TopicsService {
15 |
16 |
17 | @GET("/api/topics/show.json")
18 | void getTopicsByUserName(@Query("username") String username, Callback> callback);
19 |
20 | @GET("/api/topics/show.json")
21 | void getTopicByNodeId(@Query("node_id") int node_id, Callback> callback);
22 |
23 | @GET("/api/topics/show.json")
24 | void getTopicsByNodeName(@Query("node_name") String node_name, Callback> callback);
25 |
26 | @GET("/api/topics/hot.json")
27 | void getHot(Callback> listCallback);
28 |
29 | @GET("/api/topics/latest.json")
30 | void getLatest(Callback> listCallback);
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/model/Member.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * Created by 州 on 2015/7/18 0018.
10 | * Member的模型类
11 | */
12 | public class Member implements Serializable, Parcelable {
13 |
14 | public static final String MEMBER = "member";
15 | public static final String MEMBER_NAME = "member_name";
16 |
17 | public int id;
18 | public String username;
19 | public String website;
20 | public String twitter;
21 | public String location;
22 | public String tagline;
23 | public String bio;
24 | public String avatar_mini;
25 | public String avatar_normal;
26 | public String avatar_large;
27 | public long created;
28 |
29 | @Override
30 | public String toString() {
31 | return "Member{" +
32 | "id=" + id +
33 | ", username='" + username + '\'' +
34 | ", website='" + website + '\'' +
35 | ", twitter='" + twitter + '\'' +
36 | ", location='" + location + '\'' +
37 | ", tagline='" + tagline + '\'' +
38 | ", bio='" + bio + '\'' +
39 | ", avatar_mini='" + avatar_mini + '\'' +
40 | ", avater_normal='" + avatar_normal + '\'' +
41 | ", avatar_large='" + avatar_large + '\'' +
42 | ", created=" + created +
43 | '}';
44 | }
45 |
46 |
47 | @Override
48 | public int describeContents() {
49 | return 0;
50 | }
51 |
52 | @Override
53 | public void writeToParcel(Parcel dest, int flags) {
54 | dest.writeInt(this.id);
55 | dest.writeString(this.username);
56 | dest.writeString(this.website);
57 | dest.writeString(this.twitter);
58 | dest.writeString(this.location);
59 | dest.writeString(this.tagline);
60 | dest.writeString(this.bio);
61 | dest.writeString(this.avatar_mini);
62 | dest.writeString(this.avatar_normal);
63 | dest.writeString(this.avatar_large);
64 | dest.writeLong(this.created);
65 | }
66 |
67 | public Member() {
68 | }
69 |
70 | protected Member(Parcel in) {
71 | this.id = in.readInt();
72 | this.username = in.readString();
73 | this.website = in.readString();
74 | this.twitter = in.readString();
75 | this.location = in.readString();
76 | this.tagline = in.readString();
77 | this.bio = in.readString();
78 | this.avatar_mini = in.readString();
79 | this.avatar_normal = in.readString();
80 | this.avatar_large = in.readString();
81 | this.created = in.readLong();
82 | }
83 |
84 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
85 | public Member createFromParcel(Parcel source) {
86 | return new Member(source);
87 | }
88 |
89 | public Member[] newArray(int size) {
90 | return new Member[size];
91 | }
92 | };
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/model/Node.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * Created by 州 on 2015/7/18 0018.
10 | * Node的模型类
11 | */
12 | public class Node implements Serializable, Parcelable {
13 |
14 | public static final String NODE = "node";
15 | public static final String NODE_NAME = "node_name";
16 |
17 | public int id;
18 | public String name;
19 | public String url;
20 | public String title;
21 | public String title_alternative;
22 | public int topics;
23 | public String header;
24 | public String footer;
25 | public long created;
26 |
27 | @Override
28 | public String toString() {
29 | return "Node{" +
30 | "id=" + id +
31 | ", name='" + name + '\'' +
32 | ", url='" + url + '\'' +
33 | ", title='" + title + '\'' +
34 | ", title_alternative='" + title_alternative + '\'' +
35 | ", topics=" + topics +
36 | ", num='" + header + '\'' +
37 | ", footer='" + footer + '\'' +
38 | ", created=" + created +
39 | '}';
40 | }
41 |
42 |
43 | @Override
44 | public int describeContents() {
45 | return 0;
46 | }
47 |
48 | @Override
49 | public void writeToParcel(Parcel dest, int flags) {
50 | dest.writeInt(this.id);
51 | dest.writeString(this.name);
52 | dest.writeString(this.url);
53 | dest.writeString(this.title);
54 | dest.writeString(this.title_alternative);
55 | dest.writeInt(this.topics);
56 | dest.writeString(this.header);
57 | dest.writeString(this.footer);
58 | dest.writeLong(this.created);
59 | }
60 |
61 | public Node() {
62 | }
63 |
64 | protected Node(Parcel in) {
65 | this.id = in.readInt();
66 | this.name = in.readString();
67 | this.url = in.readString();
68 | this.title = in.readString();
69 | this.title_alternative = in.readString();
70 | this.topics = in.readInt();
71 | this.header = in.readString();
72 | this.footer = in.readString();
73 | this.created = in.readLong();
74 | }
75 |
76 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
77 | public Node createFromParcel(Parcel source) {
78 | return new Node(source);
79 | }
80 |
81 | public Node[] newArray(int size) {
82 | return new Node[size];
83 | }
84 | };
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/model/Replies.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * Created by 州 on 2015/7/18 0018.
10 | * 回复的模型类
11 | */
12 | public class Replies implements Serializable, Parcelable {
13 | public int id;
14 | public int thanks;
15 | public String content;
16 | public String content_rendered;
17 | public Member member;
18 | public long created;
19 | public long last_modified;
20 |
21 | @Override
22 | public String toString() {
23 | return "Replies{" +
24 | "id=" + id +
25 | ", thanks=" + thanks +
26 | ", content='" + content + '\'' +
27 | ", content_rendered='" + content_rendered + '\'' +
28 | ", member=" + member +
29 | ", created=" + created +
30 | ", last_modified=" + last_modified +
31 | '}';
32 | }
33 |
34 |
35 | @Override
36 | public int describeContents() {
37 | return 0;
38 | }
39 |
40 | @Override
41 | public void writeToParcel(Parcel dest, int flags) {
42 | dest.writeInt(this.id);
43 | dest.writeInt(this.thanks);
44 | dest.writeString(this.content);
45 | dest.writeString(this.content_rendered);
46 | dest.writeSerializable(this.member);
47 | dest.writeLong(this.created);
48 | dest.writeLong(this.last_modified);
49 | }
50 |
51 | public Replies() {
52 | }
53 |
54 | protected Replies(Parcel in) {
55 | this.id = in.readInt();
56 | this.thanks = in.readInt();
57 | this.content = in.readString();
58 | this.content_rendered = in.readString();
59 | this.member = (Member) in.readSerializable();
60 | this.created = in.readLong();
61 | this.last_modified = in.readLong();
62 | }
63 |
64 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
65 | public Replies createFromParcel(Parcel source) {
66 | return new Replies(source);
67 | }
68 |
69 | public Replies[] newArray(int size) {
70 | return new Replies[size];
71 | }
72 | };
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/model/Topic.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import java.io.Serializable;
7 |
8 | /**
9 | * Created by 州 on 2015/7/18 0018.
10 | * Topic的模型类
11 | */
12 | public class Topic implements Serializable, Parcelable {
13 |
14 | public static final String TOPIC = "topic";
15 |
16 | public int id;
17 | public String title;
18 | public String url;
19 | public String content;
20 | public String content_rendered;
21 | public int replies;
22 | public Member member;
23 | public Node node;
24 | public long created;
25 | public long lastModified;
26 | public long lastTouched;
27 |
28 |
29 | @Override
30 | public String toString() {
31 | return "Topic{" +
32 | "id=" + id +
33 | ", title='" + title + '\'' +
34 | ", url='" + url + '\'' +
35 | ", content='" + content + '\'' +
36 | ", content_rendered='" + content_rendered + '\'' +
37 | ", replies=" + replies +
38 | ", member=" + member +
39 | ", node=" + node +
40 | ", created=" + created +
41 | ", lastModified=" + lastModified +
42 | ", lastTouched=" + lastTouched +
43 | '}';
44 | }
45 |
46 | @Override
47 | public int describeContents() {
48 | return 0;
49 | }
50 |
51 | @Override
52 | public void writeToParcel(Parcel dest, int flags) {
53 | dest.writeInt(this.id);
54 | dest.writeString(this.title);
55 | dest.writeString(this.url);
56 | dest.writeString(this.content);
57 | dest.writeString(this.content_rendered);
58 | dest.writeInt(this.replies);
59 | dest.writeSerializable(this.member);
60 | dest.writeSerializable(this.node);
61 | dest.writeLong(this.created);
62 | dest.writeLong(this.lastModified);
63 | dest.writeLong(this.lastTouched);
64 | }
65 |
66 | public Topic() {
67 | }
68 |
69 | protected Topic(Parcel in) {
70 | this.id = in.readInt();
71 | this.title = in.readString();
72 | this.url = in.readString();
73 | this.content = in.readString();
74 | this.content_rendered = in.readString();
75 | this.replies = in.readInt();
76 | this.member = (Member) in.readSerializable();
77 | this.node = (Node) in.readSerializable();
78 | this.created = in.readLong();
79 | this.lastModified = in.readLong();
80 | this.lastTouched = in.readLong();
81 | }
82 |
83 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
84 | public Topic createFromParcel(Parcel source) {
85 | return new Topic(source);
86 | }
87 |
88 | public Topic[] newArray(int size) {
89 | return new Topic[size];
90 | }
91 | };
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/net/NetworkManager.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.net;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.support.annotation.NonNull;
6 | import android.util.Log;
7 |
8 | import com.squareup.okhttp.Callback;
9 | import com.squareup.okhttp.OkHttpClient;
10 | import com.squareup.okhttp.Request;
11 | import com.squareup.okhttp.Response;
12 |
13 | import java.io.IOException;
14 | import java.net.CookieManager;
15 | import java.net.CookiePolicy;
16 | import java.util.concurrent.TimeUnit;
17 |
18 | import com.zzhoujay.v2ex.V2EX;
19 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
20 |
21 | /**
22 | * Created by zzhoujay on 2015/7/27 0027.
23 | * 网络请求管理器
24 | */
25 | public class NetworkManager {
26 |
27 | private static NetworkManager networkManager;
28 | private OkHttpClient client;
29 | private Handler mainHandler;
30 |
31 |
32 | private NetworkManager() {
33 | client = new OkHttpClient();
34 | CookieManager manager = new CookieManager(new PersistentCookieStore(V2EX.getInstance()), CookiePolicy.ACCEPT_ALL);
35 | client.setCookieHandler(manager);
36 | client.setFollowRedirects(false);
37 | client.getCookieHandler();
38 | client.setConnectTimeout(3, TimeUnit.SECONDS);
39 | client.setReadTimeout(3, TimeUnit.SECONDS);
40 | client.setWriteTimeout(3, TimeUnit.SECONDS);
41 | mainHandler = new Handler(Looper.getMainLooper());
42 | }
43 |
44 | public static NetworkManager getInstance() {
45 | if (networkManager == null) {
46 | networkManager = new NetworkManager();
47 | }
48 | return networkManager;
49 | }
50 |
51 | /**
52 | * 获取Request.Builder
53 | *
54 | * @return builder
55 | */
56 | public Request.Builder requestBuilder() {
57 | Request.Builder builder;
58 | builder = new Request.Builder()
59 | .addHeader("Cache-Control", "max-age=0")
60 | .addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
61 | .addHeader("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7")
62 | .addHeader("Accept-Language", "zh-CN, en-US")
63 | .addHeader("Host", "v2ex.com")
64 | .addHeader("X-Requested-With", "com.android.browser")
65 | .addHeader("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.2.1; en-us; M040 Build/JOP40D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
66 | return builder;
67 | }
68 |
69 | /**
70 | * 异步请求
71 | *
72 | * @param request 请求体
73 | * @param callback 回调
74 | */
75 | public void request(Request request, final Callback callback) {
76 | client.newCall(request).enqueue(new Callback() {
77 | @Override
78 | public void onFailure(final Request request, final IOException e) {
79 | mainHandler.post(new Runnable() {
80 | @Override
81 | public void run() {
82 | if (callback != null) {
83 | callback.onFailure(request, e);
84 | }
85 | }
86 | });
87 | }
88 |
89 | @Override
90 | public void onResponse(final Response response) throws IOException {
91 | mainHandler.post(new Runnable() {
92 |
93 | @Override
94 | public void run() {
95 | if (callback != null) {
96 | try {
97 | callback.onResponse(response);
98 | } catch (IOException e) {
99 | e.printStackTrace();
100 | }
101 | }
102 | }
103 | });
104 | }
105 | });
106 | }
107 |
108 | /**
109 | * 异步请求字符串内容
110 | *
111 | * @param request 请求体
112 | * @param loadComplete 回调
113 | */
114 | public void requestString(Request request, @NonNull final OnLoadCompleteListener loadComplete) {
115 | client.newCall(request).enqueue(new Callback() {
116 | @Override
117 | public void onFailure(Request request, IOException e) {
118 | Log.d("requestString", "failure", e);
119 | mainHandler.post(new Runnable() {
120 | @Override
121 | public void run() {
122 | loadComplete.loadComplete(null);
123 | }
124 | });
125 | }
126 |
127 | @Override
128 | public void onResponse(Response response) throws IOException {
129 | Log.d("requestString", "success");
130 | final String str = response.body().string();
131 | mainHandler.post(new Runnable() {
132 | @Override
133 | public void run() {
134 | loadComplete.loadComplete(str);
135 | }
136 | });
137 | }
138 | });
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/net/SerializableHttpCookie.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.net;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.ByteArrayInputStream;
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.IOException;
8 | import java.io.ObjectInputStream;
9 | import java.io.ObjectOutputStream;
10 | import java.io.Serializable;
11 | import java.lang.reflect.Field;
12 | import java.net.HttpCookie;
13 |
14 | /**
15 | * Created by zzhoujay on 2015/7/27 0027.
16 | * 可序列化的cookie
17 | */
18 | public class SerializableHttpCookie implements Serializable {
19 | private static final String TAG = SerializableHttpCookie.class
20 | .getSimpleName();
21 |
22 | private static final long serialVersionUID = 6374381323722046732L;
23 |
24 | private transient HttpCookie cookie;
25 |
26 | // Workaround httpOnly: The httpOnly attribute is not accessible so when we
27 | // serialize and deserialize the cookie it not preserve the same value. We
28 | // need to access it using reflection
29 | private Field fieldHttpOnly;
30 |
31 | public SerializableHttpCookie() {
32 | }
33 |
34 | public String encode(HttpCookie cookie) {
35 | this.cookie = cookie;
36 |
37 | ByteArrayOutputStream os = new ByteArrayOutputStream();
38 | try {
39 | ObjectOutputStream outputStream = new ObjectOutputStream(os);
40 | outputStream.writeObject(this);
41 | } catch (IOException e) {
42 | return null;
43 | }
44 |
45 | return byteArrayToHexString(os.toByteArray());
46 | }
47 |
48 | public HttpCookie decode(String encodedCookie) {
49 | byte[] bytes = hexStringToByteArray(encodedCookie);
50 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
51 | bytes);
52 | HttpCookie cookie = null;
53 | try {
54 | ObjectInputStream objectInputStream = new ObjectInputStream(
55 | byteArrayInputStream);
56 | cookie = ((SerializableHttpCookie) objectInputStream.readObject()).cookie;
57 | } catch (IOException e) {
58 | Log.d(TAG, "IOException in decodeCookie", e);
59 | } catch (ClassNotFoundException e) {
60 | Log.d(TAG, "ClassNotFoundException in decodeCookie", e);
61 | }
62 |
63 | return cookie;
64 | }
65 |
66 | // Workaround httpOnly (getter)
67 | private boolean getHttpOnly() {
68 | try {
69 | initFieldHttpOnly();
70 | return (boolean) fieldHttpOnly.get(cookie);
71 | } catch (Exception e) {
72 | // NoSuchFieldException || IllegalAccessException ||
73 | // IllegalArgumentException
74 | Log.w(TAG, e);
75 | }
76 | return false;
77 | }
78 |
79 | // Workaround httpOnly (setter)
80 | private void setHttpOnly(boolean httpOnly) {
81 | try {
82 | initFieldHttpOnly();
83 | fieldHttpOnly.set(cookie, httpOnly);
84 | } catch (Exception e) {
85 | // NoSuchFieldException || IllegalAccessException ||
86 | // IllegalArgumentException
87 | Log.w(TAG, e);
88 | }
89 | }
90 |
91 | private void initFieldHttpOnly() throws NoSuchFieldException {
92 | fieldHttpOnly = cookie.getClass().getDeclaredField("httpOnly");
93 | fieldHttpOnly.setAccessible(true);
94 | }
95 |
96 | private void writeObject(ObjectOutputStream out) throws IOException {
97 | out.writeObject(cookie.getName());
98 | out.writeObject(cookie.getValue());
99 | out.writeObject(cookie.getComment());
100 | out.writeObject(cookie.getCommentURL());
101 | out.writeObject(cookie.getDomain());
102 | out.writeLong(cookie.getMaxAge());
103 | out.writeObject(cookie.getPath());
104 | out.writeObject(cookie.getPortlist());
105 | out.writeInt(cookie.getVersion());
106 | out.writeBoolean(cookie.getSecure());
107 | out.writeBoolean(cookie.getDiscard());
108 | out.writeBoolean(getHttpOnly());
109 | }
110 |
111 | private void readObject(ObjectInputStream in) throws IOException,
112 | ClassNotFoundException {
113 | String name = (String) in.readObject();
114 | String value = (String) in.readObject();
115 | cookie = new HttpCookie(name, value);
116 | cookie.setComment((String) in.readObject());
117 | cookie.setCommentURL((String) in.readObject());
118 | cookie.setDomain((String) in.readObject());
119 | cookie.setMaxAge(in.readLong());
120 | cookie.setPath((String) in.readObject());
121 | cookie.setPortlist((String) in.readObject());
122 | cookie.setVersion(in.readInt());
123 | cookie.setSecure(in.readBoolean());
124 | cookie.setDiscard(in.readBoolean());
125 | setHttpOnly(in.readBoolean());
126 | }
127 |
128 | /**
129 | * Using some super basic byte array <-> hex conversions so we don't
130 | * have to rely on any large Base64 libraries. Can be overridden if you
131 | * like!
132 | *
133 | * @param bytes byte array to be converted
134 | * @return string containing hex values
135 | */
136 | private String byteArrayToHexString(byte[] bytes) {
137 | StringBuilder sb = new StringBuilder(bytes.length * 2);
138 | for (byte element : bytes) {
139 | int v = element & 0xff;
140 | if (v < 16) {
141 | sb.append('0');
142 | }
143 | sb.append(Integer.toHexString(v));
144 | }
145 | return sb.toString();
146 | }
147 |
148 | /**
149 | * Converts hex values from strings to byte array
150 | *
151 | * @param hexString string of hex-encoded values
152 | * @return decoded byte array
153 | */
154 | private byte[] hexStringToByteArray(String hexString) {
155 | int len = hexString.length();
156 | byte[] data = new byte[len / 2];
157 | for (int i = 0; i < len; i += 2) {
158 | data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character
159 | .digit(hexString.charAt(i + 1), 16));
160 | }
161 | return data;
162 | }
163 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/activity/LoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.activity;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.os.Parcelable;
7 | import android.support.design.widget.TextInputLayout;
8 | import android.support.v7.app.ActionBar;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.support.v7.widget.Toolbar;
11 | import android.text.Editable;
12 | import android.text.TextUtils;
13 | import android.text.TextWatcher;
14 | import android.view.MenuItem;
15 | import android.view.View;
16 | import android.widget.Button;
17 | import android.widget.EditText;
18 |
19 | import com.zzhoujay.v2ex.R;
20 | import com.zzhoujay.v2ex.V2EX;
21 | import com.zzhoujay.v2ex.data.DataManger;
22 | import com.zzhoujay.v2ex.data.MemberProvider;
23 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
24 | import com.zzhoujay.v2ex.model.Member;
25 | import com.zzhoujay.v2ex.util.UserUtils;
26 |
27 | /**
28 | * Created by zzhoujay on 2015/7/24 0024.
29 | * 登录
30 | */
31 | public class LoginActivity extends AppCompatActivity {
32 |
33 | private EditText username, password;
34 | private ProgressDialog progressDialogUserInfo;
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_login);
40 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
41 | setSupportActionBar(toolbar);
42 | ActionBar actionBar = getSupportActionBar();
43 | if (actionBar != null) {
44 | actionBar.setDisplayHomeAsUpEnabled(true);
45 | actionBar.setDisplayShowTitleEnabled(true);
46 | }
47 | setTitle(R.string.signin);
48 |
49 | initView();
50 | }
51 |
52 | private void initView() {
53 | final TextInputLayout usernameLayout = (TextInputLayout) findViewById(R.id.login_username_layout);
54 | final TextInputLayout passwordLayout = (TextInputLayout) findViewById(R.id.login_password_layout);
55 | username = (EditText) findViewById(R.id.login_username);
56 | password = (EditText) findViewById(R.id.login_password);
57 | Button login = (Button) findViewById(R.id.login_btn);
58 |
59 | username.addTextChangedListener(new TextWatcher() {
60 | @Override
61 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
62 |
63 | }
64 |
65 | @Override
66 | public void onTextChanged(CharSequence s, int start, int before, int count) {
67 |
68 | }
69 |
70 | @Override
71 | public void afterTextChanged(Editable s) {
72 | if (TextUtils.isEmpty(username.getText())) {
73 | usernameLayout.setError(getString(R.string.username_empty));
74 | usernameLayout.setErrorEnabled(true);
75 | } else {
76 | usernameLayout.setErrorEnabled(false);
77 | }
78 | }
79 | });
80 |
81 | password.addTextChangedListener(new TextWatcher() {
82 | @Override
83 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
84 |
85 | }
86 |
87 | @Override
88 | public void onTextChanged(CharSequence s, int start, int before, int count) {
89 |
90 | }
91 |
92 | @Override
93 | public void afterTextChanged(Editable s) {
94 | if (TextUtils.isEmpty(password.getText())) {
95 | passwordLayout.setErrorEnabled(true);
96 | passwordLayout.setError(getString(R.string.password_empty));
97 | } else {
98 | passwordLayout.setErrorEnabled(false);
99 | }
100 | }
101 | });
102 |
103 | login.setOnClickListener(new View.OnClickListener() {
104 | @Override
105 | public void onClick(View v) {
106 | final String usernameStr = username.getText().toString();
107 | final String passwordStr = password.getText().toString();
108 | if (usernameStr.isEmpty()) {
109 | V2EX.getInstance().toast(R.string.username_empty);
110 | return;
111 | }
112 | if (passwordStr.isEmpty()) {
113 | V2EX.getInstance().toast(R.string.password_empty);
114 | }
115 | final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this);
116 | progressDialog.setMessage(getString(R.string.signin_progress));
117 | progressDialog.setCanceledOnTouchOutside(false);
118 | progressDialog.setCancelable(false);
119 | progressDialog.show();
120 | UserUtils.login(usernameStr, passwordStr, new OnLoadCompleteListener() {
121 | @Override
122 | public void loadComplete(Boolean aBoolean) {
123 | if (aBoolean) {
124 | V2EX.getInstance().toast(R.string.login_success);
125 | getUserInfo(usernameStr);
126 | } else {
127 | V2EX.getInstance().toast(R.string.login_error);
128 | }
129 | progressDialog.dismiss();
130 | }
131 | });
132 | }
133 | });
134 |
135 | }
136 |
137 | private void getUserInfo(String username) {
138 | MemberProvider memberProvider = new MemberProvider(DataManger.getInstance().getRestAdapter(), username, true);
139 | DataManger.getInstance().addProvider(memberProvider.FILE_NAME, memberProvider, true);
140 | progressDialogUserInfo = new ProgressDialog(LoginActivity.this);
141 | progressDialogUserInfo.setMessage(getString(R.string.get_user_info));
142 | progressDialogUserInfo.show();
143 | DataManger.getInstance().getData(MemberProvider.SElF, onLoadListener);
144 | }
145 |
146 | private OnLoadCompleteListener onLoadListener = new OnLoadCompleteListener() {
147 | @Override
148 | public void loadComplete(Member member) {
149 | V2EX.getInstance().setSelf(member);
150 | progressDialogUserInfo.dismiss();
151 | Intent intent = new Intent(LoginActivity.this, MemberActivity.class);
152 | intent.putExtra(Member.MEMBER, (Parcelable) member);
153 | startActivity(intent);
154 | finish();
155 | }
156 | };
157 |
158 | @Override
159 | public boolean onOptionsItemSelected(MenuItem item) {
160 | switch (item.getItemId()) {
161 | case android.R.id.home:
162 | finish();
163 | return true;
164 | }
165 | return super.onOptionsItemSelected(item);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/activity/MemberActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.support.design.widget.AppBarLayout;
7 | import android.support.design.widget.CollapsingToolbarLayout;
8 | import android.support.v7.app.ActionBar;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.support.v7.widget.Toolbar;
11 | import android.text.TextUtils;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.widget.ImageView;
15 | import android.widget.TextView;
16 |
17 |
18 | import com.bumptech.glide.Glide;
19 | import com.zzhoujay.v2ex.R;
20 | import com.zzhoujay.v2ex.V2EX;
21 | import com.zzhoujay.v2ex.data.DataManger;
22 | import com.zzhoujay.v2ex.data.MemberProvider;
23 | import com.zzhoujay.v2ex.data.TopicsProvider;
24 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
25 | import com.zzhoujay.v2ex.model.Member;
26 | import com.zzhoujay.v2ex.ui.fragment.TopicsFragment;
27 | import com.zzhoujay.v2ex.util.UserUtils;
28 |
29 | /**
30 | * Created by zzhoujay on 2015/7/22 0022.
31 | * Member详细资料
32 | */
33 | public class MemberActivity extends AppCompatActivity implements AppBarLayout.OnOffsetChangedListener {
34 |
35 | private CollapsingToolbarLayout collapsingToolbarLayout;
36 | private Member member;
37 | private ImageView icon;
38 | private TextView name;
39 | private AppBarLayout appBarLayout;
40 | private MemberProvider memberProvider;
41 | private TopicsFragment topicsFragment;
42 |
43 | @Override
44 | protected void onCreate(Bundle savedInstanceState) {
45 | super.onCreate(savedInstanceState);
46 | setContentView(R.layout.activity_member);
47 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
48 | setSupportActionBar(toolbar);
49 | ActionBar actionBar = getSupportActionBar();
50 | if (actionBar != null) {
51 | actionBar.setDisplayHomeAsUpEnabled(true);
52 | actionBar.setDisplayHomeAsUpEnabled(true);
53 | actionBar.setDisplayShowTitleEnabled(true);
54 | }
55 |
56 | Intent intent = getIntent();
57 | Uri uri = intent.getData();
58 | String username = null;
59 | if (uri != null) {
60 | username = uri.getPathSegments().get(1);
61 | } else {
62 | if (intent.hasExtra(Member.MEMBER)) {
63 | member = intent.getParcelableExtra(Member.MEMBER);
64 | } else if (intent.hasExtra(Member.MEMBER_NAME)) {
65 | username = intent.getStringExtra(Member.MEMBER_NAME);
66 | }
67 | }
68 |
69 |
70 | initView();
71 |
72 |
73 | if (username != null) {
74 | memberProvider = new MemberProvider(DataManger.getInstance().getRestAdapter()
75 | , username, V2EX.getInstance().isSelf(username));
76 | DataManger.getInstance().addProvider(memberProvider.FILE_NAME, memberProvider);
77 | DataManger.getInstance().getData(memberProvider.FILE_NAME, memberOnLoadComplete);
78 | } else if (member != null) {
79 | initData(member);
80 | }
81 |
82 | }
83 |
84 | private void initData(Member member) {
85 | if (member != null) {
86 | collapsingToolbarLayout.setTitle(member.username);
87 | name.setText(TextUtils.isEmpty(member.bio) ? getString(R.string.describe_empty) : member.bio);
88 | Glide
89 | .with(this)
90 | .load("http:" + member.avatar_large)
91 | .placeholder(R.mipmap.ic_launcher)
92 | .error(R.mipmap.ic_launcher)
93 | .centerCrop()
94 | .crossFade()
95 | .into(icon);
96 | topicsFragment = TopicsFragment.newInstance(TopicsProvider.TopicType.newTopicTypeByUserName("member_topic_" + member.username, member.username));
97 | getSupportFragmentManager().beginTransaction().add(R.id.member_fragment, topicsFragment).commit();
98 | }
99 | }
100 |
101 | private void initView() {
102 | collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
103 | appBarLayout = (AppBarLayout) findViewById(R.id.appBar);
104 | icon = (ImageView) findViewById(R.id.member_icon);
105 | name = (TextView) findViewById(R.id.member_name);
106 |
107 |
108 | }
109 |
110 | private OnLoadCompleteListener memberOnLoadComplete = new OnLoadCompleteListener() {
111 | @Override
112 | public void loadComplete(Member member) {
113 | initData(member);
114 | }
115 | };
116 |
117 | @Override
118 | public boolean onCreateOptionsMenu(Menu menu) {
119 | if (V2EX.getInstance().isSelf(member == null ? null : member.username)) {
120 | MenuItem item = menu.add(0, 10086, 1, R.string.logout);
121 | item.setIcon(R.drawable.ic_exit_to_app_white);
122 | item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
123 | MenuItem item1 = menu.add(0, 10010, 0, R.string.refresh);
124 | item1.setIcon(R.drawable.ic_refresh_white);
125 | item1.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
126 | }
127 |
128 | return super.onCreateOptionsMenu(menu);
129 | }
130 |
131 | @Override
132 | public boolean onOptionsItemSelected(MenuItem item) {
133 | switch (item.getItemId()) {
134 | case android.R.id.home:
135 | finish();
136 | return true;
137 | case 10086:
138 | if (V2EX.getInstance().isNetworkConnected()) {
139 | if (UserUtils.logout()) {
140 | V2EX.getInstance().toast(R.string.logout_success);
141 | finish();
142 | }
143 | } else {
144 | V2EX.getInstance().toast(R.string.network_error);
145 | }
146 | return true;
147 | case 10010:
148 | if (member != null && V2EX.getInstance().isSelf(member.username)) {
149 | DataManger.getInstance().refresh(MemberProvider.SElF, new OnLoadCompleteListener() {
150 | @Override
151 | public void loadComplete(Member member) {
152 | if (member != null) {
153 | initData(member);
154 | V2EX.getInstance().toast(R.string.refresh_success);
155 | } else {
156 | V2EX.getInstance().toast(R.string.refresh_error);
157 | }
158 | }
159 | });
160 | }
161 | return true;
162 | }
163 | return super.onOptionsItemSelected(item);
164 | }
165 |
166 | @Override
167 | public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
168 | if (topicsFragment != null) {
169 | topicsFragment.setSwipeRefreshEnable(i == 0);
170 | }
171 | }
172 |
173 | @Override
174 | protected void onResume() {
175 | super.onResume();
176 | appBarLayout.addOnOffsetChangedListener(this);
177 | }
178 |
179 | @Override
180 | protected void onPause() {
181 | super.onPause();
182 | appBarLayout.removeOnOffsetChangedListener(this);
183 | }
184 |
185 | @Override
186 | protected void onDestroy() {
187 | super.onDestroy();
188 | if (member != null && memberProvider != null && V2EX.getInstance().isSelf(member.username)) {
189 | DataManger.getInstance().removeProvider(memberProvider.FILE_NAME);
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/activity/NewTopicActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.activity;
2 |
3 | import android.app.ProgressDialog;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.ActionBar;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.support.v7.widget.Toolbar;
9 | import android.text.Editable;
10 | import android.text.TextWatcher;
11 | import android.util.Log;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.widget.EditText;
15 |
16 | import com.zzhoujay.v2ex.R;
17 | import com.zzhoujay.v2ex.V2EX;
18 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
19 | import com.zzhoujay.v2ex.model.Node;
20 | import com.zzhoujay.v2ex.util.UserUtils;
21 |
22 | /**
23 | * Created by zzhoujay on 2015/7/28 0028.
24 | * 新建主题
25 | */
26 | public class NewTopicActivity extends AppCompatActivity {
27 |
28 | private String nodeName;
29 | private MenuItem item;
30 | private EditText title, content;
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_new_topic);
36 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
37 | setSupportActionBar(toolbar);
38 | ActionBar actionBar = getSupportActionBar();
39 | if (actionBar != null) {
40 | actionBar.setDisplayShowTitleEnabled(true);
41 | actionBar.setDisplayHomeAsUpEnabled(true);
42 | }
43 |
44 | setTitle(R.string.new_topic);
45 |
46 | Intent intent = getIntent();
47 | if (intent.hasExtra(Node.NODE_NAME)) {
48 | nodeName = intent.getStringExtra(Node.NODE_NAME);
49 | }
50 | title = (EditText) findViewById(R.id.new_topic_title);
51 | content = (EditText) findViewById(R.id.new_topic_content);
52 |
53 | title.addTextChangedListener(new TextWatcher() {
54 | @Override
55 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
56 |
57 | }
58 |
59 | @Override
60 | public void onTextChanged(CharSequence s, int start, int before, int count) {
61 |
62 | }
63 |
64 | @Override
65 | public void afterTextChanged(Editable s) {
66 | if (title.getText().toString().isEmpty()) {
67 | item.setEnabled(false);
68 | } else {
69 | item.setEnabled(true);
70 | }
71 | }
72 | });
73 | }
74 |
75 | @Override
76 | public boolean onCreateOptionsMenu(Menu menu) {
77 | item = menu.add(0, 10086, 0, R.string.new_topic_publish);
78 | item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
79 | item.setIcon(R.drawable.ic_done_white);
80 | return super.onCreateOptionsMenu(menu);
81 | }
82 |
83 | @Override
84 | public boolean onOptionsItemSelected(MenuItem item) {
85 | switch (item.getItemId()) {
86 | case android.R.id.home:
87 | finish();
88 | return true;
89 | case 10086:
90 | Log.d("test", "clicked:" + nodeName);
91 | String c = content.getText().toString();
92 | if (c.isEmpty()) {
93 | V2EX.getInstance().toast(R.string.content_empty);
94 | } else {
95 | if (nodeName != null) {
96 | String t = title.getText().toString();
97 | if (t.isEmpty()) {
98 | V2EX.getInstance().toast(R.string.title_empty);
99 | } else {
100 | final ProgressDialog progressDialog = new ProgressDialog(NewTopicActivity.this);
101 | progressDialog.setMessage(getString(R.string.new_topic_progress));
102 | progressDialog.show();
103 | UserUtils.createTopic(nodeName, t, c, new OnLoadCompleteListener() {
104 | @Override
105 | public void loadComplete(Boolean aBoolean) {
106 | progressDialog.dismiss();
107 | if (aBoolean) {
108 | V2EX.getInstance().toast(R.string.new_topic_success);
109 | finish();
110 | } else {
111 | V2EX.getInstance().toast(R.string.new_topic_error);
112 | }
113 | }
114 | });
115 | }
116 | }
117 | }
118 | return true;
119 | }
120 | return super.onOptionsItemSelected(item);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/activity/NodeActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.design.widget.FloatingActionButton;
6 | import android.support.v7.app.ActionBar;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.support.v7.widget.Toolbar;
9 | import android.view.MenuItem;
10 | import android.view.View;
11 |
12 | import com.zzhoujay.v2ex.R;
13 | import com.zzhoujay.v2ex.V2EX;
14 | import com.zzhoujay.v2ex.data.TopicsProvider;
15 | import com.zzhoujay.v2ex.model.Node;
16 | import com.zzhoujay.v2ex.ui.fragment.TopicsFragment;
17 |
18 | /**
19 | * Created by 州 on 2015/7/20 0020.
20 | * Node详情
21 | */
22 | public class NodeActivity extends AppCompatActivity {
23 |
24 | private Node node;
25 | private FloatingActionButton floatingActionButton;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_fragment_floatactionbar);
31 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
32 | setSupportActionBar(toolbar);
33 | ActionBar actionBar = getSupportActionBar();
34 | if (actionBar != null) {
35 | actionBar.setDisplayUseLogoEnabled(true);
36 | actionBar.setDisplayShowTitleEnabled(true);
37 | actionBar.setDisplayShowHomeEnabled(true);
38 | actionBar.setDisplayHomeAsUpEnabled(true);
39 | }
40 | floatingActionButton = (FloatingActionButton) findViewById(R.id.floatingActionBar);
41 |
42 | Intent intent = getIntent();
43 | if (intent.hasExtra(Node.NODE)) {
44 | node = intent.getParcelableExtra(Node.NODE);
45 | setTitle(node.name);
46 | TopicsProvider.TopicType topicType = TopicsProvider.TopicType.newTopicTypeByNodeId("node_" + node.id, node.id);
47 | TopicsFragment topicsFragment = TopicsFragment.newInstance(topicType);
48 | getSupportFragmentManager().beginTransaction().add(R.id.fragment_content, topicsFragment).commit();
49 | floatingActionButton.setOnClickListener(new View.OnClickListener() {
50 | @Override
51 | public void onClick(View v) {
52 | if (node != null) {
53 | Intent i = new Intent(NodeActivity.this, NewTopicActivity.class);
54 | i.putExtra(Node.NODE_NAME, node.name);
55 | startActivity(i);
56 | }
57 | }
58 | });
59 | }
60 | }
61 |
62 | @Override
63 | protected void onResume() {
64 | super.onResume();
65 | if (V2EX.getInstance().isLogin()) {
66 | floatingActionButton.setVisibility(View.VISIBLE);
67 | } else {
68 | floatingActionButton.setVisibility(View.INVISIBLE);
69 | }
70 | }
71 |
72 | @Override
73 | public boolean onOptionsItemSelected(final MenuItem item) {
74 | switch (item.getItemId()) {
75 | case android.R.id.home:
76 | finish();
77 | return true;
78 | }
79 | return super.onOptionsItemSelected(item);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/activity/NodesActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.activity;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.ActionBar;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.Toolbar;
7 | import android.view.MenuItem;
8 |
9 | import com.zzhoujay.v2ex.R;
10 | import com.zzhoujay.v2ex.ui.fragment.NodesFragment;
11 |
12 | /**
13 | * Created by 州 on 2015/7/20 0020.
14 | * Node列表
15 | */
16 | public class NodesActivity extends AppCompatActivity {
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_fragment);
22 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
23 | setSupportActionBar(toolbar);
24 | ActionBar actionBar = getSupportActionBar();
25 | if (actionBar != null) {
26 | actionBar.setDisplayUseLogoEnabled(true);
27 | actionBar.setDisplayShowTitleEnabled(true);
28 | actionBar.setDisplayShowHomeEnabled(true);
29 | actionBar.setDisplayHomeAsUpEnabled(true);
30 | }
31 | setTitle(R.string.all_node_list);
32 | NodesFragment nodesFragment = NodesFragment.newInstance();
33 | getSupportFragmentManager().beginTransaction().add(R.id.fragment_content, nodesFragment).commit();
34 | }
35 |
36 | @Override
37 | public boolean onOptionsItemSelected(MenuItem item) {
38 | switch (item.getItemId()) {
39 | case android.R.id.home:
40 | finish();
41 | return true;
42 | }
43 | return super.onOptionsItemSelected(item);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/activity/TopicDetailActivity.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBar;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.Toolbar;
8 | import android.view.MenuItem;
9 |
10 | import com.zzhoujay.v2ex.R;
11 | import com.zzhoujay.v2ex.V2EX;
12 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
13 | import com.zzhoujay.v2ex.model.Replies;
14 | import com.zzhoujay.v2ex.model.Topic;
15 | import com.zzhoujay.v2ex.ui.fragment.ReplyFragment;
16 | import com.zzhoujay.v2ex.ui.fragment.TopicDetailFragment;
17 |
18 | /**
19 | * Created by 州 on 2015/7/20 0020.
20 | * Topic详情
21 | */
22 | public class TopicDetailActivity extends AppCompatActivity {
23 |
24 | @Override
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_fragment);
28 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
29 | setSupportActionBar(toolbar);
30 | ActionBar actionBar = getSupportActionBar();
31 | if (actionBar != null) {
32 | actionBar.setDisplayUseLogoEnabled(true);
33 | actionBar.setDisplayShowTitleEnabled(true);
34 | actionBar.setDisplayShowHomeEnabled(true);
35 | actionBar.setDisplayHomeAsUpEnabled(true);
36 | }
37 | setTitle(R.string.topic_detail);
38 | Intent intent = getIntent();
39 | if (intent.hasExtra(Topic.TOPIC)) {
40 | Topic topic = intent.getParcelableExtra(Topic.TOPIC);
41 | final TopicDetailFragment topicDetailFragment = TopicDetailFragment.newInstance(topic);
42 | getSupportFragmentManager().beginTransaction().add(R.id.fragment_content, topicDetailFragment).commit();
43 | // if (V2EX.getInstance().isLogin()) {
44 | // final ReplyFragment replyFragment = ReplyFragment.newInstance(topic);
45 | // replyFragment.setOnReplySuccessListener(new ReplyFragment.OnReplySuccessListener() {
46 | // @Override
47 | // public void replySuccess() {
48 | // topicDetailFragment.refresh();
49 | // topicDetailFragment.scrollToBottom();
50 | // }
51 | // });
52 | // getSupportFragmentManager().beginTransaction().add(R.id.fragment_bottom, replyFragment).commit();
53 | //
54 | // topicDetailFragment.setOnItemClickCallback(new OnLoadCompleteListener() {
55 | // @Override
56 | // public void loadComplete(Replies replies) {
57 | // String content = "@" + replies.member.username + " ";
58 | // replyFragment.setContent(content);
59 | // replyFragment.setSelection(content.length());
60 | // }
61 | // });
62 | // }
63 |
64 | }
65 | }
66 |
67 | @Override
68 | public boolean onOptionsItemSelected(final MenuItem item) {
69 | switch (item.getItemId()) {
70 | case android.R.id.home:
71 | finish();
72 | return true;
73 | }
74 | return super.onOptionsItemSelected(item);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/adapter/NodesAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.adapter;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.TextView;
8 |
9 | import java.util.List;
10 |
11 | import com.zzhoujay.richtext.RichText;
12 | import com.zzhoujay.v2ex.R;
13 | import com.zzhoujay.v2ex.interfaces.ClickCallback;
14 | import com.zzhoujay.v2ex.interfaces.OnItemClickListener;
15 | import com.zzhoujay.v2ex.model.Node;
16 | import com.zzhoujay.v2ex.util.ContentUtils;
17 |
18 | /**
19 | * Created by 州 on 2015/7/20 0020.
20 | * Node列表的Adapter
21 | */
22 | public class NodesAdapter extends RecyclerView.Adapter {
23 |
24 | private List nodes;
25 | private ClickCallback clickCallback;
26 |
27 | public NodesAdapter(List nodes) {
28 | this.nodes = nodes;
29 | }
30 |
31 | private OnItemClickListener onItemClickListener = new OnItemClickListener() {
32 | @Override
33 | public void onItemClicked(View view, int position) {
34 | Node node = nodes.get(position);
35 | if (clickCallback != null) {
36 | clickCallback.callback(node);
37 | }
38 | }
39 | };
40 |
41 |
42 | @Override
43 | public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
44 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_node, null);
45 | Holder holder = new Holder(view);
46 | holder.setOnItemClickListener(onItemClickListener);
47 | return holder;
48 | }
49 |
50 | @Override
51 | public void onBindViewHolder(Holder holder, int position) {
52 | Node node = nodes.get(position);
53 | holder.title.setText(node.title);
54 | RichText.from(ContentUtils.formatContent(node.header)).into(holder.content);
55 | holder.num.setText(node.topics + "个主题");
56 | }
57 |
58 | @Override
59 | public int getItemCount() {
60 | return nodes == null ? 0 : nodes.size();
61 | }
62 |
63 | public static class Holder extends RecyclerView.ViewHolder {
64 |
65 | public TextView title, num;
66 | public TextView content;
67 |
68 | private View parent;
69 | private OnItemClickListener onItemClickListener;
70 |
71 | public Holder(View itemView) {
72 | super(itemView);
73 | parent = itemView;
74 | title = (TextView) itemView.findViewById(R.id.item_node_title);
75 | content = (TextView) itemView.findViewById(R.id.item_node_content);
76 | num = (TextView) itemView.findViewById(R.id.item_node_num);
77 |
78 | parent.setOnClickListener(new View.OnClickListener() {
79 | @Override
80 | public void onClick(View v) {
81 | if (onItemClickListener != null) {
82 | onItemClickListener.onItemClicked(parent, getAdapterPosition());
83 | }
84 | }
85 | });
86 | }
87 |
88 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
89 | this.onItemClickListener = onItemClickListener;
90 | }
91 | }
92 |
93 | public void setNodes(List nodes) {
94 | this.nodes = nodes;
95 | notifyDataSetChanged();
96 | }
97 |
98 | public void setClickCallback(ClickCallback clickCallback) {
99 | this.clickCallback = clickCallback;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/adapter/RepliesAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.adapter;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import java.util.List;
11 |
12 | import com.bumptech.glide.Glide;
13 | import com.zzhoujay.richtext.RichText;
14 | import com.zzhoujay.v2ex.R;
15 | import com.zzhoujay.v2ex.interfaces.OnItemClickListener;
16 | import com.zzhoujay.v2ex.model.Replies;
17 | import com.zzhoujay.v2ex.util.ContentUtils;
18 | import com.zzhoujay.v2ex.util.TimeUtils;
19 |
20 | /**
21 | * Created by 州 on 2015/7/20 0020.
22 | * 回复列表Adapter
23 | */
24 | public class RepliesAdapter extends RecyclerView.Adapter {
25 |
26 | private List replies;
27 | private OnItemClickListener iconClickCallback;
28 | private OnItemClickListener itemClickCallback;
29 |
30 | public RepliesAdapter(List replies) {
31 | this.replies = replies;
32 | }
33 |
34 |
35 | private OnItemClickListener onIconClickListener = new OnItemClickListener() {
36 | @Override
37 | public void onItemClicked(View view, int position) {
38 | if (iconClickCallback != null) {
39 | iconClickCallback.onItemClicked(view, position);
40 | }
41 | }
42 | };
43 |
44 | private OnItemClickListener onItemClickListener = new OnItemClickListener() {
45 | @Override
46 | public void onItemClicked(View view, int position) {
47 | if (itemClickCallback != null) {
48 | itemClickCallback.onItemClicked(view, position);
49 | }
50 | }
51 | };
52 |
53 | @Override
54 | public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
55 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_replies, null);
56 | Holder holder = new Holder(view);
57 | holder.setIconClickListener(onIconClickListener);
58 | holder.setOnItemClickListener(onItemClickListener);
59 | return holder;
60 | }
61 |
62 | @Override
63 | public void onBindViewHolder(RecyclerView.ViewHolder h, int position) {
64 | if (h instanceof Holder) {
65 | Holder holder= (Holder) h;
66 | Replies reply = replies.get(position);
67 | holder.user.setText(reply.member.username);
68 | holder.time.setText(TimeUtils.friendlyFormat(reply.created * 1000));
69 | holder.floor.setText(String.format("%d楼", (position + 1)));
70 | RichText.from(ContentUtils.formatContent(reply.content_rendered)).into(holder.content);
71 | Glide
72 | .with(holder.icon.getContext())
73 | .load("http:" + reply.member.avatar_normal)
74 | .placeholder(R.drawable.default_image)
75 | .crossFade()
76 | .centerCrop()
77 | .into(holder.icon);
78 | }
79 | }
80 |
81 | @Override
82 | public int getItemCount() {
83 | return replies == null ? 0 : replies.size();
84 | }
85 |
86 | public static class Holder extends RecyclerView.ViewHolder {
87 |
88 | public ImageView icon;
89 | public TextView user, time, floor;
90 | public TextView content;
91 |
92 | private OnItemClickListener iconClickListener;
93 | private OnItemClickListener onItemClickListener;
94 |
95 | public Holder(final View itemView) {
96 | super(itemView);
97 | icon = (ImageView) itemView.findViewById(R.id.item_replies_icon);
98 | user = (TextView) itemView.findViewById(R.id.item_replies_user);
99 | time = (TextView) itemView.findViewById(R.id.item_replies_time);
100 | floor = (TextView) itemView.findViewById(R.id.item_replies_floor);
101 | content = (TextView) itemView.findViewById(R.id.item_replies_content);
102 |
103 | icon.setOnClickListener(new View.OnClickListener() {
104 | @Override
105 | public void onClick(View v) {
106 | if (iconClickListener != null) {
107 | iconClickListener.onItemClicked(icon, getLayoutPosition());
108 | getAdapterPosition();
109 | }
110 | }
111 | });
112 |
113 | itemView.setOnClickListener(new View.OnClickListener() {
114 | @Override
115 | public void onClick(View v) {
116 | if (onItemClickListener != null) {
117 | onItemClickListener.onItemClicked(itemView, getAdapterPosition());
118 | }
119 | }
120 | });
121 | }
122 |
123 | public void setIconClickListener(OnItemClickListener iconClickListener) {
124 | this.iconClickListener = iconClickListener;
125 | }
126 |
127 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
128 | this.onItemClickListener = onItemClickListener;
129 | }
130 | }
131 |
132 | public Replies getItem(int position) {
133 | return replies == null ? null : replies.get(position);
134 | }
135 |
136 | public void setReplies(List replies) {
137 | this.replies = replies;
138 | notifyDataSetChanged();
139 | }
140 |
141 | public void setIconClickCallback(OnItemClickListener iconClickCallback) {
142 | this.iconClickCallback = iconClickCallback;
143 | }
144 |
145 | public void setItemClickCallback(OnItemClickListener itemClickCallback) {
146 | this.itemClickCallback = itemClickCallback;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/adapter/TopicsAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.adapter;
2 |
3 | import android.support.annotation.Nullable;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 |
11 |
12 | import java.util.List;
13 |
14 | import com.bumptech.glide.Glide;
15 | import com.zzhoujay.v2ex.R;
16 | import com.zzhoujay.v2ex.interfaces.ClickCallback;
17 | import com.zzhoujay.v2ex.interfaces.OnItemClickListener;
18 | import com.zzhoujay.v2ex.model.Topic;
19 | import com.zzhoujay.v2ex.util.TimeUtils;
20 |
21 | /**
22 | * Created by 州 on 2015/7/20 0020.
23 | * Topic列表Adapter
24 | */
25 | public class TopicsAdapter extends RecyclerView.Adapter {
26 |
27 | private List topics;
28 | private ClickCallback clickCallback;
29 |
30 | public TopicsAdapter(@Nullable List topics) {
31 | this.topics = topics;
32 | }
33 |
34 | private OnItemClickListener onItemClickListener = new OnItemClickListener() {
35 | @Override
36 | public void onItemClicked(View view, int position) {
37 | Topic topic = topics.get(position);
38 | if (clickCallback != null) {
39 | clickCallback.callback(topic);
40 | }
41 | }
42 | };
43 |
44 | @Override
45 | public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
46 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_topic, null);
47 | Holder holder = new Holder(view);
48 | holder.setOnItemClickListener(onItemClickListener);
49 | return holder;
50 | }
51 |
52 | @Override
53 | public void onBindViewHolder(Holder holder, int position) {
54 | Topic topic = topics.get(position);
55 |
56 | holder.title.setText(topic.title);
57 | holder.node.setText(topic.node.title);
58 | holder.user.setText(topic.member.username);
59 | holder.time.setText(TimeUtils.friendlyFormat(topic.created * 1000));
60 | holder.reply.setText(String.format("%d个回复", topic.replies));
61 | Glide.with(holder.icon.getContext())
62 | .load("http:" + topic.member.avatar_normal)
63 | .placeholder(R.drawable.default_image)
64 | .centerCrop()
65 | .crossFade()
66 | .into(holder.icon);
67 | }
68 |
69 | @Override
70 | public int getItemCount() {
71 | return topics == null ? 0 : topics.size();
72 | }
73 |
74 | public static class Holder extends RecyclerView.ViewHolder {
75 |
76 | public TextView title, node, user, time, reply;
77 | public ImageView icon;
78 |
79 | private View parent;
80 | private OnItemClickListener onItemClickListener;
81 |
82 | public Holder(View itemView) {
83 | super(itemView);
84 | parent = itemView;
85 | title = (TextView) itemView.findViewById(R.id.item_topic_title);
86 | node = (TextView) itemView.findViewById(R.id.item_topic_node);
87 | user = (TextView) itemView.findViewById(R.id.item_topic_user);
88 | time = (TextView) itemView.findViewById(R.id.item_topic_time);
89 | icon = (ImageView) itemView.findViewById(R.id.item_topic_icon);
90 | reply = (TextView) itemView.findViewById(R.id.item_topic_reply);
91 |
92 | itemView.setOnClickListener(new View.OnClickListener() {
93 | @Override
94 | public void onClick(View v) {
95 | if (onItemClickListener != null) {
96 | onItemClickListener.onItemClicked(parent, getAdapterPosition());
97 | }
98 | }
99 | });
100 | }
101 |
102 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
103 | this.onItemClickListener = onItemClickListener;
104 | }
105 | }
106 |
107 | public void setTopics(List topics) {
108 | this.topics = topics;
109 | notifyDataSetChanged();
110 | }
111 |
112 | public void setClickCallback(ClickCallback clickCallback) {
113 | this.clickCallback = clickCallback;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/dialog/ContentDialog.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.dialog;
2 |
3 | import android.app.Dialog;
4 | import android.os.Bundle;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.app.DialogFragment;
8 | import android.support.v7.app.AlertDialog;
9 |
10 | import com.zzhoujay.v2ex.R;
11 |
12 | /**
13 | * Created by zzhoujay on 2015/7/28 0028.
14 | * 显示内容的Dialog
15 | */
16 | public class ContentDialog extends DialogFragment {
17 |
18 | public static final String CONTENT = "content";
19 | public static final String TITLE = "title";
20 |
21 | private CharSequence content;
22 | private String title;
23 |
24 | @Override
25 | public void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | Bundle bundle = getArguments();
28 | if (bundle.containsKey(CONTENT)) {
29 | content = bundle.getString(CONTENT);
30 | }
31 | if (bundle.containsKey(TITLE)) {
32 | title = bundle.getString(TITLE);
33 | }
34 | }
35 |
36 | @NonNull
37 | @Override
38 | public Dialog onCreateDialog(Bundle savedInstanceState) {
39 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
40 | if (title != null) {
41 | builder.setTitle(title);
42 | }
43 | if (content != null) {
44 | builder.setMessage(content);
45 | }
46 | builder.setPositiveButton(R.string.confirm, null);
47 | return builder.create();
48 | }
49 |
50 | public static ContentDialog newInstance(@Nullable String title, @Nullable CharSequence content) {
51 | ContentDialog contentDialog = new ContentDialog();
52 | Bundle bundle = new Bundle();
53 | bundle.putString(TITLE, title);
54 | bundle.putCharSequence(CONTENT, content);
55 | contentDialog.setArguments(bundle);
56 | return contentDialog;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/fragment/NodesFragment.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.fragment;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Parcelable;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.app.Fragment;
8 | import android.support.v4.widget.SwipeRefreshLayout;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.support.v7.widget.StaggeredGridLayoutManager;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 |
15 | import java.util.List;
16 |
17 | import com.zzhoujay.v2ex.R;
18 | import com.zzhoujay.v2ex.data.DataManger;
19 | import com.zzhoujay.v2ex.data.NodesProvider;
20 | import com.zzhoujay.v2ex.interfaces.ClickCallback;
21 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
22 | import com.zzhoujay.v2ex.model.Node;
23 | import com.zzhoujay.v2ex.ui.activity.NodeActivity;
24 | import com.zzhoujay.v2ex.ui.adapter.NodesAdapter;
25 |
26 | /**
27 | * Created by 州 on 2015/7/20 0020.
28 | * 显示Node列表的Fragment
29 | */
30 | public class NodesFragment extends Fragment {
31 |
32 | private RecyclerView recyclerView;
33 | private SwipeRefreshLayout swipeRefreshLayout;
34 | private NodesAdapter nodesAdapter;
35 |
36 | @Override
37 | public void onCreate(@Nullable Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | NodesProvider nodesProvider = new NodesProvider(DataManger.getInstance().getRestAdapter());
40 | DataManger.getInstance().addProvider(NodesProvider.FILE_NAME, nodesProvider);
41 | }
42 |
43 | @Nullable
44 | @Override
45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
46 | View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
47 | swipeRefreshLayout = (SwipeRefreshLayout) view;
48 | swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_purple, android.R.color.holo_blue_bright, android.R.color.holo_orange_light,
49 | android.R.color.holo_red_light);
50 | recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
51 | StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
52 | recyclerView.setLayoutManager(staggeredGridLayoutManager);
53 | swipeRefreshLayout.setRefreshing(true);
54 | DataManger.getInstance().getData(NodesProvider.FILE_NAME, new OnLoadCompleteListener>() {
55 | @Override
56 | public void loadComplete(List nodes) {
57 | setUp(nodes);
58 | }
59 | });
60 | swipeRefreshLayout.setOnRefreshListener(onRefreshListener);
61 | return view;
62 | }
63 |
64 | private SwipeRefreshLayout.OnRefreshListener onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
65 | @Override
66 | public void onRefresh() {
67 | DataManger.getInstance().refresh(NodesProvider.FILE_NAME, onLoadComplete);
68 | }
69 | };
70 |
71 | private OnLoadCompleteListener> onLoadComplete = new OnLoadCompleteListener>() {
72 | @Override
73 | public void loadComplete(List nodes) {
74 | if (nodes != null) {
75 | nodesAdapter.setNodes(nodes);
76 | }
77 | swipeRefreshLayout.setRefreshing(false);
78 | }
79 | };
80 |
81 | private ClickCallback clickCallback = new ClickCallback() {
82 | @Override
83 | public void callback(Node node) {
84 | Intent intent = new Intent(getActivity(), NodeActivity.class);
85 | intent.putExtra(Node.NODE, (Parcelable) node);
86 | startActivity(intent);
87 | }
88 | };
89 |
90 | private void setUp(List nodes) {
91 | swipeRefreshLayout.setRefreshing(false);
92 | nodesAdapter = new NodesAdapter(nodes);
93 | nodesAdapter.setClickCallback(clickCallback);
94 | recyclerView.setAdapter(nodesAdapter);
95 | }
96 |
97 | public static NodesFragment newInstance() {
98 | NodesFragment nodesFragment;
99 | nodesFragment = new NodesFragment();
100 | return nodesFragment;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/fragment/ReplyFragment.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.fragment;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v4.app.Fragment;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.EditText;
10 | import android.widget.ImageButton;
11 |
12 | import com.zzhoujay.v2ex.R;
13 | import com.zzhoujay.v2ex.V2EX;
14 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
15 | import com.zzhoujay.v2ex.model.Topic;
16 | import com.zzhoujay.v2ex.util.UserUtils;
17 |
18 | /**
19 | * Created by zzhoujay on 2015/7/27 0027.
20 | * 回复栏
21 | */
22 | public class ReplyFragment extends Fragment {
23 |
24 | private EditText editText;
25 | private Topic topic;
26 | private OnReplySuccessListener onReplySuccessListener;
27 |
28 | @Override
29 | public void onCreate(@Nullable Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | Bundle bundle = getArguments();
32 | if (bundle != null && bundle.containsKey(Topic.TOPIC)) {
33 | topic = bundle.getParcelable(Topic.TOPIC);
34 | }
35 | }
36 |
37 | @Nullable
38 | @Override
39 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
40 | View view = inflater.inflate(R.layout.fragment_reply, container, false);
41 | editText = (EditText) view.findViewById(R.id.reply_content);
42 | ImageButton imageButton = (ImageButton) view.findViewById(R.id.reply_btn);
43 | imageButton.setOnClickListener(replyListener);
44 | return view;
45 | }
46 |
47 | private View.OnClickListener replyListener = new View.OnClickListener() {
48 | @Override
49 | public void onClick(View v) {
50 | String content = editText.getText().toString();
51 | if (content.isEmpty()) {
52 | V2EX.getInstance().toast(R.string.empty_reply);
53 | } else {
54 | if (!V2EX.getInstance().isNetworkConnected()) {
55 | V2EX.getInstance().toast(R.string.network_error);
56 | } else {
57 | if (topic != null) {
58 | UserUtils.replyTopic(topic.id, content, new OnLoadCompleteListener() {
59 | @Override
60 | public void loadComplete(Boolean aBoolean) {
61 | if (aBoolean) {
62 | V2EX.getInstance().toast(R.string.reply_success);
63 | editText.setText("");
64 | if (onReplySuccessListener != null) {
65 | onReplySuccessListener.replySuccess();
66 | }
67 | } else {
68 | V2EX.getInstance().toast(R.string.reply_error);
69 | }
70 | }
71 | });
72 | }
73 | }
74 | }
75 | }
76 | };
77 |
78 | public void setContent(String content) {
79 | editText.setText(content);
80 | }
81 |
82 | public void setSelection(int len) {
83 | editText.setSelection(len);
84 | }
85 |
86 | public void setOnReplySuccessListener(OnReplySuccessListener onReplySuccessListener) {
87 | this.onReplySuccessListener = onReplySuccessListener;
88 | }
89 |
90 | public static ReplyFragment newInstance(@Nullable Topic topic) {
91 | ReplyFragment replyFragment = new ReplyFragment();
92 | Bundle bundle = new Bundle();
93 | bundle.putParcelable(Topic.TOPIC, topic);
94 | replyFragment.setArguments(bundle);
95 | return replyFragment;
96 | }
97 |
98 | public interface OnReplySuccessListener {
99 | void replySuccess();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/fragment/TopicsFragment.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.fragment;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Parcelable;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.v4.app.Fragment;
9 | import android.support.v4.widget.SwipeRefreshLayout;
10 | import android.support.v7.widget.LinearLayoutManager;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.util.Log;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 |
17 | import java.util.List;
18 |
19 | import com.zzhoujay.v2ex.R;
20 | import com.zzhoujay.v2ex.data.DataManger;
21 | import com.zzhoujay.v2ex.data.TopicsProvider;
22 | import com.zzhoujay.v2ex.interfaces.ClickCallback;
23 | import com.zzhoujay.v2ex.interfaces.OnLoadCompleteListener;
24 | import com.zzhoujay.v2ex.model.Topic;
25 | import com.zzhoujay.v2ex.ui.activity.TopicDetailActivity;
26 | import com.zzhoujay.v2ex.ui.adapter.TopicsAdapter;
27 |
28 | /**
29 | * Created by 州 on 2015/7/20 0020.
30 | * Topic列表
31 | */
32 | public class TopicsFragment extends Fragment {
33 |
34 | public static final String TYPE = "type";
35 |
36 | private RecyclerView recyclerView;
37 | private SwipeRefreshLayout swipeRefreshLayout;
38 | private TopicsProvider.TopicType topicType;
39 | private TopicsAdapter topicsAdapter;
40 |
41 | @Override
42 | public void onCreate(@Nullable Bundle savedInstanceState) {
43 | super.onCreate(savedInstanceState);
44 | Bundle bundle = getArguments();
45 | if (bundle.containsKey(TYPE)) {
46 | topicType = bundle.getParcelable(TYPE);
47 | if (topicType != null) {
48 | TopicsProvider topicsProvider = new TopicsProvider(DataManger.getInstance().getRestAdapter(), topicType);
49 | DataManger.getInstance().addProvider(topicType.fileName, topicsProvider);
50 | }
51 | }
52 | }
53 |
54 | @Nullable
55 | @Override
56 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
57 | View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
58 | swipeRefreshLayout = (SwipeRefreshLayout) view;
59 | swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_purple, android.R.color.holo_blue_bright, android.R.color.holo_orange_light,
60 | android.R.color.holo_red_light);
61 | recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
62 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
63 | recyclerView.setLayoutManager(linearLayoutManager);
64 | setUp(null);
65 | swipeRefreshLayout.setRefreshing(true);
66 | DataManger.getInstance().getData(topicType.fileName, new OnLoadCompleteListener>() {
67 | @Override
68 | public void loadComplete(List topics) {
69 | if (topics != null && topics.size() > 0) {
70 | topicsAdapter.setTopics(topics);
71 | }
72 | swipeRefreshLayout.setRefreshing(false);
73 | }
74 | });
75 | swipeRefreshLayout.setOnRefreshListener(onRefreshListener);
76 | return view;
77 | }
78 |
79 | private void setUp(List topics) {
80 | swipeRefreshLayout.setRefreshing(false);
81 | topicsAdapter = new TopicsAdapter(topics);
82 | topicsAdapter.setClickCallback(clickCallback);
83 | recyclerView.setAdapter(topicsAdapter);
84 | }
85 |
86 | private SwipeRefreshLayout.OnRefreshListener onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
87 | @Override
88 | public void onRefresh() {
89 | DataManger.getInstance().refresh(topicType.fileName, refreshListener);
90 | }
91 | };
92 |
93 | private OnLoadCompleteListener> refreshListener = new OnLoadCompleteListener>() {
94 | @Override
95 | public void loadComplete(List topics) {
96 | if (topics != null && topics.size() > 0) {
97 | topicsAdapter.setTopics(topics);
98 | }
99 | swipeRefreshLayout.setRefreshing(false);
100 | }
101 | };
102 |
103 | private ClickCallback clickCallback = new ClickCallback() {
104 | @Override
105 | public void callback(Topic topic) {
106 | Intent intent = new Intent(getActivity(), TopicDetailActivity.class);
107 | intent.putExtra(Topic.TOPIC, (Parcelable) topic);
108 | startActivity(intent);
109 | }
110 | };
111 |
112 | public void setSwipeRefreshEnable(boolean enable) {
113 | try {
114 | swipeRefreshLayout.setEnabled(enable);
115 | } catch (Exception e) {
116 | Log.d("setSwipeRefreshEnable", "error", e);
117 | }
118 | }
119 |
120 |
121 | @Override
122 | public void onDestroy() {
123 | super.onDestroy();
124 | recyclerView.setAdapter(null);
125 | recyclerView = null;
126 | if (!topicType.fileName.equals(TopicsProvider.TopicType.FILE_NAME_HOT) && !topicType.fileName.equals(TopicsProvider.TopicType.FILE_NAME_LATEST)) {
127 | DataManger.getInstance().removeProvider(topicType.fileName);
128 | }
129 | }
130 |
131 | public static TopicsFragment newInstance(@NonNull TopicsProvider.TopicType topicType) {
132 | TopicsFragment topicsFragment = new TopicsFragment();
133 | Bundle bundle = new Bundle();
134 | bundle.putParcelable(TYPE, topicType);
135 | topicsFragment.setArguments(bundle);
136 | return topicsFragment;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/ui/view/SwipeToRefreshLayout.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.ui.view;
2 |
3 | import android.content.Context;
4 | import android.support.v4.widget.SwipeRefreshLayout;
5 | import android.util.AttributeSet;
6 |
7 | /**
8 | * Created by zzhoujay on 2015/8/10 0010.
9 | * 继承至SwipeRefreshLayout,修复了setRefreshing(true)时不显示的BUG
10 | */
11 | public class SwipeToRefreshLayout extends SwipeRefreshLayout {
12 |
13 | public SwipeToRefreshLayout(Context context) {
14 | super(context);
15 | }
16 |
17 | public SwipeToRefreshLayout(Context context, AttributeSet attrs) {
18 | super(context, attrs);
19 | }
20 |
21 | private boolean mMeasured = false;
22 | private boolean mPreMeasureRefreshing = false;
23 |
24 | @Override
25 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
26 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
27 | if (!mMeasured) {
28 | mMeasured = true;
29 | setRefreshing(mPreMeasureRefreshing);
30 | }
31 | }
32 |
33 |
34 | @Override
35 | public void setRefreshing(boolean refreshing) {
36 | if (mMeasured) {
37 | super.setRefreshing(refreshing);
38 | } else {
39 | mPreMeasureRefreshing = refreshing;
40 | }
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/util/ContentUtils.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.util;
2 |
3 | /**
4 | * Created by zzhoujay on 2015/7/23 0023.
5 | * ContentUtil
6 | */
7 | public class ContentUtils {
8 | public static String formatContent(String content) {
9 | if (content == null) {
10 | return "";
11 | }
12 | return content.replace("href=\"/member/", "href=\"http://www.v2ex.com/member/")
13 | .replace("href=\"/i/", "href=\"https://i.v2ex.co/")
14 | .replace("href=\"/t/", "href=\"http://www.v2ex.com/t/")
15 | .replace("href=\"/go/", "href=\"http://www.v2ex.com/go/");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/util/FileComparator.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.util;
2 |
3 | import java.io.File;
4 | import java.util.Comparator;
5 |
6 | /**
7 | * Created by zzhoujay on 2015/7/22 0022.
8 | * 文件比较器
9 | */
10 | public class FileComparator implements Comparator {
11 | @Override
12 | public int compare(File lhs, File rhs) {
13 | return (int) (rhs.lastModified() - lhs.lastModified());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/util/FileNameFilter.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.util;
2 |
3 | import java.io.File;
4 | import java.io.FilenameFilter;
5 |
6 | /**
7 | * Created by zzhoujay on 2015/7/22 0022.
8 | * 文件过滤
9 | */
10 | public class FileNameFilter implements FilenameFilter {
11 |
12 | private String start;
13 |
14 | public FileNameFilter(String start) {
15 | this.start = start;
16 | }
17 |
18 | @Override
19 | public boolean accept(File dir, String filename) {
20 | return filename != null && filename.startsWith(start);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/util/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.util;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.FileOutputStream;
8 | import java.io.IOException;
9 | import java.io.ObjectInputStream;
10 | import java.io.ObjectOutputStream;
11 | import java.text.DecimalFormat;
12 | import java.util.Arrays;
13 |
14 | /**
15 | * Created by 州 on 2015/7/4 0004.
16 | * 文件操作相关工具类
17 | */
18 | public class FileUtils {
19 |
20 | public static DecimalFormat decimalFormat = new DecimalFormat(".00");
21 |
22 | /**
23 | * 判断文件是否存在
24 | *
25 | * @param path 文件的全路径
26 | * @return 文件是否存在
27 | */
28 | @SuppressWarnings("unused")
29 | public static boolean isFileExists(String path) {
30 | File file = new File(path);
31 | return file.exists();
32 | }
33 |
34 | @SuppressWarnings("unused")
35 | public static boolean isFileExists(File parent, String name) {
36 | File file = new File(parent, name);
37 | return file.exists();
38 | }
39 |
40 | /**
41 | * 获取文件的后缀名
42 | *
43 | * @param path 文件全路径
44 | * @return 后缀名
45 | */
46 | @SuppressWarnings("unused")
47 | public static String getFileExtension(String path) {
48 | return path == null ? null : path.substring(path.lastIndexOf(".") + 1);
49 | }
50 |
51 | /**
52 | * 去掉路径的后缀名
53 | *
54 | * @param path 文件全路径
55 | * @return 去后缀名后的文件名
56 | */
57 | @SuppressWarnings("unused")
58 | public static String getPathWithoutExtension(String path) {
59 | return path == null ? null : path.substring(0, path.lastIndexOf("."));
60 | }
61 |
62 | /**
63 | * 将对象写入指定路径的文件中
64 | *
65 | * @param path 文件路径
66 | * @param obj 需要被写入的对象
67 | */
68 | @SuppressWarnings("unused")
69 | public static void writeObject(String path, Object obj) {
70 | File file = new File(path);
71 | writeObject(file, obj);
72 | }
73 |
74 | /**
75 | * 写入对象到文件中
76 | *
77 | * @param file 文件对象
78 | * @param obj 需要写入文件的对象
79 | */
80 | @SuppressWarnings("unused")
81 | public static void writeObject(File file, Object obj) {
82 | if (null == file || obj == null) {
83 | return;
84 | }
85 | FileOutputStream fileOutputStream = null;
86 | ObjectOutputStream objectOutputStream = null;
87 |
88 | try {
89 | fileOutputStream = new FileOutputStream(file);
90 | objectOutputStream = new ObjectOutputStream(fileOutputStream);
91 |
92 | objectOutputStream.writeObject(obj);
93 | objectOutputStream.flush();
94 | } catch (IOException e) {
95 | Log.d("writeObject", e.getMessage());
96 | } finally {
97 | try {
98 | if (objectOutputStream != null) {
99 | objectOutputStream.close();
100 | }
101 | if (fileOutputStream != null) {
102 | fileOutputStream.close();
103 | }
104 | } catch (IOException e) {
105 | Log.d("writeObject", e.getMessage());
106 | }
107 | }
108 | }
109 |
110 | /**
111 | * 从指定路径的文件中读取对象
112 | *
113 | * @param path 文件路径
114 | * @return 读取到的对象
115 | */
116 | @SuppressWarnings("unused")
117 | public static Object readObject(String path) {
118 | File file = new File(path);
119 | return readObject(file);
120 | }
121 |
122 | /**
123 | * 从文件中读取对象
124 | *
125 | * @param file 文件对象
126 | * @return 读取到的对象
127 | */
128 | public static Object readObject(File file) {
129 | Object obj = null;
130 | if (file != null && file.exists()) {
131 | FileInputStream fileInputStream = null;
132 | ObjectInputStream objectInputStream = null;
133 |
134 | try {
135 | fileInputStream = new FileInputStream(file);
136 | objectInputStream = new ObjectInputStream(fileInputStream);
137 |
138 | obj = objectInputStream.readObject();
139 | } catch (IOException | ClassNotFoundException e) {
140 | Log.d("readObject", e.getMessage());
141 | } finally {
142 | try {
143 | if (objectInputStream != null) {
144 | objectInputStream.close();
145 | }
146 | if (fileInputStream != null) {
147 | fileInputStream.close();
148 | }
149 | } catch (IOException e) {
150 | Log.d("readObject", e.getMessage());
151 | }
152 |
153 | }
154 | }
155 | return obj;
156 | }
157 |
158 | /**
159 | * 格式化文件大小以字符串输出
160 | *
161 | * @param size 大小
162 | * @return B、KB、MB类型的字符串
163 | */
164 | @SuppressWarnings("unused")
165 | public static String formatSize(int size) {
166 | if (size < 1024 * 0.6) {
167 | return size + "B";
168 | } else if (size < 1024 * 1024 * 0.6) {
169 | return decimalFormat.format((float) size / 1024) + "KB";
170 | } else {
171 | return decimalFormat.format((float) size / (1024 * 1024)) + "MB";
172 | }
173 | }
174 |
175 | @SuppressWarnings("unused")
176 | public static boolean createFolder(File file, String name) {
177 | if (file.exists()) {
178 | File f = new File(file, name);
179 | if (!f.exists()) {
180 | return f.mkdirs();
181 | }
182 | }
183 | return false;
184 | }
185 |
186 | @SuppressWarnings("unused")
187 | public static void deleteSomeCache(File parent, String start, int maxSize) {
188 | if(parent!=null){
189 | File[] files = parent.listFiles(new FileNameFilter(start));
190 | if (files != null && files.length > maxSize) {
191 | Arrays.sort(files, new FileComparator());
192 | for (int len = files.length, i = maxSize; i < len; i++) {
193 | files[i].delete();
194 | }
195 | }
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zzhoujay/v2ex/util/TimeUtils.java:
--------------------------------------------------------------------------------
1 | package com.zzhoujay.v2ex.util;
2 |
3 | import java.text.SimpleDateFormat;
4 | import java.util.Calendar;
5 | import java.util.Date;
6 |
7 | /**
8 | * Created by 州 on 2015/7/20 0020.
9 | * TimeUtils
10 | */
11 | public class TimeUtils {
12 |
13 | /**
14 | * 友好的方式显示时间
15 | */
16 | public static String friendlyFormat(long time) {
17 | Date date = new Date(time);
18 |
19 | Calendar now = getCal();
20 | String t = new SimpleDateFormat("HH:mm").format(date);
21 |
22 | // 第一种情况,日期在同一天
23 | String curDate = dateFormat.get().format(now.getTime());
24 | String paramDate = dateFormat.get().format(date);
25 | if (curDate.equals(paramDate)) {
26 | int hour = (int) ((now.getTimeInMillis() - date.getTime()) / 3600000);
27 | if (hour > 0)
28 | return t;
29 | int minute = (int) ((now.getTimeInMillis() - date.getTime()) / 60000);
30 | if (minute < 2)
31 | return "刚刚";
32 | if (minute > 30)
33 | return "半个小时以前";
34 | return minute + "分钟前";
35 | }
36 |
37 | // 第二种情况,不在同一天
38 | int days = (int) ((getBegin(getDate()).getTime() - getBegin(date).getTime()) / 86400000);
39 | if (days == 1)
40 | return "昨天 " + t;
41 | if (days == 2)
42 | return "前天 " + t;
43 | if (days <= 7)
44 | return days + "天前";
45 | return dateToStr(date);
46 | }
47 |
48 | /**
49 | * 返回日期的0点:2012-07-07 20:20:20 --> 2012-07-07 00:00:00
50 | */
51 | public static Date getBegin(Date date) {
52 | return strToTime(dateToStr(date) + " 00:00:00");
53 | }
54 |
55 | /**
56 | * 日期格式
57 | */
58 | private final static ThreadLocal dateFormat = new ThreadLocal() {
59 | protected SimpleDateFormat initialValue() {
60 | return new SimpleDateFormat("yyyy-MM-dd");
61 | }
62 | };
63 |
64 | /**
65 | * 时间格式
66 | */
67 | private final static ThreadLocal timeFormat = new ThreadLocal() {
68 | protected SimpleDateFormat initialValue() {
69 | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
70 | }
71 | };
72 |
73 | /**
74 | * 获取当前时间:Date
75 | */
76 | public static Date getDate() {
77 | return new Date();
78 | }
79 |
80 | /**
81 | * 获取当前时间:Calendar
82 | */
83 | public static Calendar getCal() {
84 | return Calendar.getInstance();
85 | }
86 |
87 | /**
88 | * 日期转换为字符串:yyyy-MM-dd
89 | */
90 | public static String dateToStr(Date date) {
91 | if (date != null)
92 | return dateFormat.get().format(date);
93 | return null;
94 | }
95 |
96 | /**
97 | * 字符串转换为时间:yyyy-MM-dd HH:mm:ss
98 | */
99 | public static Date strToTime(String str) {
100 | Date date = null;
101 | try {
102 | date = timeFormat.get().parse(str);
103 | } catch (Exception e) {
104 | e.printStackTrace();
105 | }
106 | return date;
107 | }
108 |
109 | /**
110 | * 字符串转换为日期:yyyy-MM-dd
111 | */
112 | @SuppressWarnings("unused")
113 | public static Date strToDate(String str) {
114 | Date date = null;
115 | try {
116 | date = dateFormat.get().parse(str);
117 | } catch (Exception e) {
118 | e.printStackTrace();
119 | }
120 | return date;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_add_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_add_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_dashboard_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_dashboard_grey.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_done_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_done_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_drawer.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_exit_to_app_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_exit_to_app_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_info_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_info_grey.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_refresh_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_refresh_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_send_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_send_grey.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_settings_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_settings_grey.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_view_agenda_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-hdpi/ic_view_agenda_grey.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-mdpi/ic_drawer.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_add_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xhdpi/ic_add_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_done_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xhdpi/ic_done_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xhdpi/ic_drawer.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_exit_to_app_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xhdpi/ic_exit_to_app_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_refresh_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xhdpi/ic_refresh_white.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_send_grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xhdpi/ic_send_grey.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/drawable-xxhdpi/ic_drawer.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/default_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/image_btn_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
25 |
26 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fragment_floatactionbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
21 |
22 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
24 |
25 |
35 |
36 |
46 |
47 |
54 |
55 |
60 |
61 |
62 |
70 |
71 |
77 |
78 |
79 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
21 |
22 |
27 |
28 |
33 |
34 |
35 |
36 |
41 |
42 |
43 |
44 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_member.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
24 |
25 |
35 |
36 |
47 |
48 |
49 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
75 |
76 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_new_topic.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
24 |
25 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
23 |
24 |
38 |
39 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_recycler_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_reply.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
27 |
28 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_topic_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
27 |
28 |
38 |
39 |
48 |
49 |
58 |
59 |
69 |
70 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
89 |
90 |
97 |
98 |
105 |
106 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_node.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
30 |
31 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_replies.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
27 |
28 |
36 |
37 |
47 |
48 |
57 |
58 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_topic.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
26 |
27 |
35 |
36 |
47 |
48 |
57 |
58 |
68 |
69 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/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 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 | 112sp
7 | 56sp
8 | 45sp
9 | 34sp
10 | 24sp
11 | 21sp
12 | 17sp
13 | 15sp
14 | 13sp
15 | 15sp
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | V2EX
3 |
4 | Hello world!
5 | Settings
6 | 热门主题
7 | 最新主题
8 | 关于
9 | 设置
10 | 网络连接错误,请检查网络连接后重试
11 | 加载失败
12 | 未知错误
13 | 主题详情
14 | 所有节点列表
15 | 点击登录
16 | 主题
17 | 节点
18 | 没有相关话题
19 | 请输入用户名
20 | 请输入密码
21 | 登录
22 | 登录中
23 | 登录失败
24 | 登录成功
25 | 获取用户信息
26 | 回复不能为空
27 | 回复成功
28 | 回复失败
29 | 请输入评论内容
30 | 收藏
31 | 收藏成功
32 | 收藏失败
33 | 取消收藏成功
34 | 创建新主题
35 | 发布
36 | 内容不能为空
37 | 发布成功
38 | 发布失败
39 | 正在发布
40 | 标题不能为空
41 | 请输入标题
42 | 请输入内容
43 | 退出登录
44 | 身份信息过期,请重新登录
45 | 退出成功
46 | 创意工作者们的社区
47 | 确认
48 | 本产品是V2EX非官方Android客户端,极力遵循Material Design设计原则\n\ngithub:https://github.com/zzhoujay/V2EX \n\nby zzhoujay
49 | 刷新
50 | 更新信息成功
51 | 其他
52 | 更新失败
53 | 他太懒了,没有任何描叙
54 | 用户名不能为空
55 | 密码不能为空
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/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 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.2.3'
9 | // classpath fileTree(dir: 'E:\\Other SDK\\OneAPM_Android_Gradle_2.0.0\\plugin', include: ['*.jar'])
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/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/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/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 |
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-06-28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-06-28.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-06-34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-06-34.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-06-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-06-40.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-07-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-07-02.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-07-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-07-32.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-07-43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-07-43.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-07-55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-07-55.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-29-23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-29-23.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-30-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-30-44.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-19-37-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-19-37-09.png
--------------------------------------------------------------------------------
/screenshot/Screenshot_2015-07-28-21-20-39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzhoujay/V2EX/be1ee2a9f73bcd085b51e6f448cfde59b6a8dd97/screenshot/Screenshot_2015-07-28-21-20-39.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------