diff --git a/app/build.gradle b/app/build.gradle
index 4f5b8271e8cf52d8c02c376ae4c25ac68094c018..c6d07b1c3628c07c65c8fdf0934b2c72c8f49046 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'com.android.application'
+apply plugin: 'android'
apply plugin: 'newlens'
android {
@@ -13,13 +13,15 @@ android {
versionName "2.4"
}
+ /*
signingConfigs {
release {
}
}
+
buildTypes {
debug {
- signingConfig signingConfigs.release
+ storeFile file("debug.keystore")
}
release {
signingConfig signingConfigs.release
@@ -31,6 +33,7 @@ android {
lintOptions {
abortOnError false
}
+ */
}
dependencies {
@@ -44,9 +47,11 @@ dependencies {
compile 'com.networkbench.newlens.agent.android:nbs.newlens.agent:2.2.5'
compile 'com.google.zxing:core:3.2.0'
compile 'com.joanzapata.android:android-iconify:1.0.9'
+ compile 'com.belerweb:pinyin4j:2.5.0'
}
// 配置签名文件以及相关的账号信息
+/*
File propFile = new File('sign.properties')
if (propFile.exists()) {
def Properties props = new Properties()
@@ -64,3 +69,4 @@ if (propFile.exists()) {
} else {
android.buildTypes.release.signingConfig = null
}
+*/
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 72cd19687ef19ff23acd295323e61fdb5750bc91..7fba0ecf54d992eebe6b4ee7ce41a29a0301c6ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
+ package="net.oschina.app" >
@@ -121,21 +121,24 @@
-
+
-
-
+
+
-
+ android:screenOrientation="portrait"
+ android:windowSoftInputMode="stateHidden|adjustResize" />
-
-
-
+ android:screenOrientation="portrait" />
-
+
+
@@ -191,12 +195,12 @@
-
+
-
+
-
+ android:screenOrientation="portrait"
+ android:theme="@style/Theme.Transparent" />
+
+
diff --git a/app/src/main/java/net/oschina/app/AppException.java b/app/src/main/java/net/oschina/app/AppException.java
index 5f76356f2ac00cae8a5ea9d7779c2fbd13a23c6e..75eeda4a9af3ada31a9eef3c7004c9b3adc11b06 100644
--- a/app/src/main/java/net/oschina/app/AppException.java
+++ b/app/src/main/java/net/oschina/app/AppException.java
@@ -154,6 +154,7 @@ public class AppException extends Exception implements UncaughtExceptionHandler
}
boolean success = true;
try {
+ ex.printStackTrace();
success = saveToSDCard(ex);
} catch (Exception e) {
} finally {
diff --git a/app/src/main/java/net/oschina/app/api/ApiHttpClient.java b/app/src/main/java/net/oschina/app/api/ApiHttpClient.java
index 59613bf921f94f47a8d5f45bcfebb3042234355c..1059570600022a860a6d09de72966ef57aa5f082 100644
--- a/app/src/main/java/net/oschina/app/api/ApiHttpClient.java
+++ b/app/src/main/java/net/oschina/app/api/ApiHttpClient.java
@@ -18,8 +18,8 @@ public class ApiHttpClient {
// public final static String HOST = "www.oschina.net";
// private static String API_URL = "http://www.oschina.net/%s";
- public final static String HOST = "192.168.1.107";
- private static String API_URL = "http://192.168.1.107/%s";
+ public final static String HOST = "www.oschina.net";
+ private static String API_URL = "http://www.oschina.net/%s";
public static final String DELETE = "DELETE";
public static final String GET = "GET";
public static final String POST = "POST";
diff --git a/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java b/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java
index a671887902f4deccb74db2bb2a7596a5af72bcf7..6d7f153d26a57587c4c0edbb47cf28712053b2ab 100644
--- a/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java
+++ b/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java
@@ -1007,4 +1007,16 @@ public class OSChinaApi {
ApiHttpClient.post("action/api/openid_reg", params, handler);
}
+ /**
+ * 获取全部好友
+ * @param uid 用户id
+ * @param handler
+ */
+ public static void getAllFriendList(int uid,AsyncHttpResponseHandler handler) {
+ RequestParams params = new RequestParams();
+ params.put("uid", uid);
+ params.put("all", 1);
+ params.put("relation", 1);
+ ApiHttpClient.get("action/api/friends_list", params, handler);
+ }
}
diff --git a/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java
index 27558fa962309fc9eead182388be9e80cf06afb7..8cc9b83811c2dec439a03b677aedf94684a074fa 100644
--- a/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java
+++ b/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java
@@ -36,6 +36,7 @@ import net.oschina.app.emoji.Emojicon;
import net.oschina.app.emoji.InputHelper;
import net.oschina.app.emoji.OnEmojiClickListener;
import net.oschina.app.service.ServerTaskUtils;
+import net.oschina.app.ui.SelectUserActivity;
import net.oschina.app.util.DialogHelp;
import net.oschina.app.util.FileUtil;
import net.oschina.app.util.ImageUtils;
@@ -64,6 +65,8 @@ import butterknife.InjectView;
public class TweetPubFragment extends BaseFragment implements
OnEmojiClickListener {
+ private static final int REQUEST_CODE_AT_USER = 0x1225;
+
public static final int ACTION_TYPE_ALBUM = 0;
public static final int ACTION_TYPE_PHOTO = 1;
public static final int ACTION_TYPE_RECORD = 2; // 录音
@@ -412,6 +415,14 @@ public class TweetPubFragment extends BaseFragment implements
final Intent imageReturnIntent) {
if (resultCode != Activity.RESULT_OK)
return;
+
+ // at user return
+ if(requestCode == REQUEST_CODE_AT_USER){
+ String atUsersText = imageReturnIntent.getStringExtra(SelectUserActivity.RESULT_AT_USERS);
+ if(!TextUtils.isEmpty(atUsersText))
+ mEtInput.getText().replace(mEtInput.getSelectionStart(),mEtInput.getSelectionEnd(),atUsersText);
+ }
+
new Thread() {
private String selectedImagePath;
@@ -595,30 +606,32 @@ public class TweetPubFragment extends BaseFragment implements
}
private void insertMention() {
- TDevice.showSoftKeyboard(mEtInput);
+ // TDevice.showSoftKeyboard(mEtInput);
// 在光标所在处插入“@用户名”
- int curTextLength = mEtInput.getText().length();
- if (curTextLength >= MAX_TEXT_LENGTH)
- return;
- String atme = TEXT_ATME;
- int start, end;
- if ((MAX_TEXT_LENGTH - curTextLength) >= atme.length()) {
- start = mEtInput.getSelectionStart() + 1;
- end = start + atme.length() - 2;
- } else {
- int num = MAX_TEXT_LENGTH - curTextLength;
- if (num < atme.length()) {
- atme = atme.substring(0, num);
- }
- start = mEtInput.getSelectionStart() + 1;
- end = start + atme.length() - 1;
- }
- if (start > MAX_TEXT_LENGTH || end > MAX_TEXT_LENGTH) {
- start = MAX_TEXT_LENGTH;
- end = MAX_TEXT_LENGTH;
- }
- mEtInput.getText().insert(mEtInput.getSelectionStart(), atme);
- mEtInput.setSelection(start, end);// 设置选中文字
+// int curTextLength = mEtInput.getText().length();
+// if (curTextLength >= MAX_TEXT_LENGTH)
+// return;
+// String atme = TEXT_ATME;
+// int start, end;
+// if ((MAX_TEXT_LENGTH - curTextLength) >= atme.length()) {
+// start = mEtInput.getSelectionStart() + 1;
+// end = start + atme.length() - 2
+// } else {
+// int num = MAX_TEXT_LENGTH - curTextLength;
+// if (num < atme.length()) {
+// atme = atme.substring(0, num);
+// }
+// start = mEtInput.getSelectionStart() + 1;
+// end = start + atme.length() - 1;
+// }
+// if (start > MAX_TEXT_LENGTH || end > MAX_TEXT_LENGTH) {
+// start = MAX_TEXT_LENGTH;
+// end = MAX_TEXT_LENGTH;
+// }
+// mEtInput.getText().insert(mEtInput.getSelectionStart(), atme);
+// mEtInput.setSelection(start, end);// 设置选中文字
+ Intent selectUserIntent = new Intent(getActivity(), SelectUserActivity.class);
+ startActivityForResult(selectUserIntent,REQUEST_CODE_AT_USER);
}
private void insertTrendSoftware() {
diff --git a/app/src/main/java/net/oschina/app/ui/SelectUserActivity.java b/app/src/main/java/net/oschina/app/ui/SelectUserActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..acdd6b7a6e44463b6db50fc6ea07292a7b49e424
--- /dev/null
+++ b/app/src/main/java/net/oschina/app/ui/SelectUserActivity.java
@@ -0,0 +1,697 @@
+package net.oschina.app.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.inputmethodservice.Keyboard;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.support.v7.app.ActionBar;
+
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.loopj.android.http.AsyncHttpResponseHandler;
+import net.oschina.app.AppContext;
+import net.oschina.app.R;
+import net.oschina.app.api.remote.OSChinaApi;
+import net.oschina.app.base.BaseActivity;
+import net.oschina.app.bean.Friend;
+import net.oschina.app.bean.FriendsList;
+import net.oschina.app.ui.empty.EmptyLayout;
+import net.oschina.app.util.StringUtils;
+import net.oschina.app.util.XmlUtils;
+import net.oschina.app.widget.AlphabetBar;
+import net.oschina.app.widget.ImageGrid;
+import net.oschina.app.widget.InputConnectEditText;
+
+import org.apache.http.Header;
+import org.kymjs.kjframe.KJBitmap;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+
+/**
+ *
+ * @author Darcy www.darcye.com
+ *
+ */
+public class SelectUserActivity extends BaseActivity {
+
+ private static final String HIGHLIGHT_COLOR = "#cc0000";
+ private static final String HIGHLIGHT_REPLACEMENT = String.format("$1",HIGHLIGHT_COLOR);
+ private static final char OTHER_INDEX_CHAR = Character.MAX_VALUE;
+
+ private static final int MSG_DATA_PROCESS = 0x10000;
+ private static final int MSG_DATA_PROCESS_SUCCESS = 0x10001;
+
+ private static final int MSG_SEARCH_FRIENDS = 0x20000;
+ private static final int MSG_SEARCH_FRIENDS_SUCCESS = 0x20001;
+
+ private static final int MSG_REFLESH_SEARCH_BAR = 0x30000;
+
+ private static final int MAX_SELECT_USER_COUNT = 10;
+
+ public static final String RESULT_AT_USERS = "at_users";
+
+ private EmptyLayout mEmptyLayout;
+ private TextView mTvConfirm;
+ private RelativeLayout mLayoutSearchBar;
+ private InputConnectEditText mEtSearch;
+ private HorizontalScrollView mHsvSelectedUsers;
+ private ImageGrid mIgSelectedUsers;
+ private AlphabetBar mAlphaBar;
+ private TextView mTvAlphaTip;
+ private ListView mLvFriends;
+ private FriendListAdapter mLvAdapter;
+
+ private List mAllMdlFriends;
+ private List mShowMdlFriends;
+ private Set mSelectedFriends = new LinkedHashSet<>();
+ private Map mFriendIndexs;
+
+ private HandlerThread mBackgroundThread;
+ private BackgroundHandler mBackgroundHandler;
+
+ private KJBitmap mkjBitmap;
+ private String mSearchText;
+ private int mSelectUserPicSize;
+ private int mTotalFriendSize;
+
+ private boolean mEtSearchWidthChanged;
+ private int mMinEtSearchWidth;
+
+ private Handler mUIHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if(msg.what == MSG_DATA_PROCESS_SUCCESS){
+ mLvAdapter = new FriendListAdapter();
+ mLvFriends.setAdapter(mLvAdapter);
+ initAlphaBar();
+ mEmptyLayout.setVisibility(View.GONE);
+ }else if(msg.what == MSG_SEARCH_FRIENDS_SUCCESS){
+ mLvAdapter.notifyDataSetChanged();
+ }else if(msg.what == MSG_REFLESH_SEARCH_BAR){
+ final int etWidth = mEtSearch.getWidth();
+ final int searchBarWidth = mLayoutSearchBar.getWidth();
+ final int selectUsersWidth = mIgSelectedUsers.getWidth();
+ if(etWidth < mMinEtSearchWidth && !mEtSearchWidthChanged){
+ ViewGroup.LayoutParams shorterLp = mHsvSelectedUsers.getLayoutParams();
+ shorterLp.width = searchBarWidth - etWidth;
+ mHsvSelectedUsers.setLayoutParams(shorterLp);
+ mEtSearchWidthChanged = true;
+ }else if(mEtSearchWidthChanged && (searchBarWidth - selectUsersWidth) > mMinEtSearchWidth){
+ ViewGroup.LayoutParams normalLp = mHsvSelectedUsers.getLayoutParams();
+ normalLp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ mHsvSelectedUsers.setLayoutParams(normalLp);
+ mEtSearchWidthChanged = false;
+ }
+
+ }
+ }
+ };
+
+ private AsyncHttpResponseHandler mFriendsListHandler =new AsyncHttpResponseHandler() {
+ @Override
+ public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
+ FriendsList friendList = XmlUtils.toBean(FriendsList.class, responseBody);
+ if(friendList == null || friendList.getFriendlist().isEmpty()){
+ mEmptyLayout.setErrorType(EmptyLayout.NODATA);
+ }else{
+ mBackgroundHandler.sendMessage(mBackgroundHandler.obtainMessage(MSG_DATA_PROCESS,friendList));
+ }
+ }
+
+ @Override
+ public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
+ mEmptyLayout.setErrorType(EmptyLayout.NETWORK_ERROR);
+ }
+ };
+
+ private TextWatcher mSearchTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ final String searchText = s.toString();
+ mSearchText = searchText;
+ mBackgroundHandler.removeMessages(MSG_SEARCH_FRIENDS);
+ mBackgroundHandler.sendMessageDelayed(mBackgroundHandler.obtainMessage(MSG_SEARCH_FRIENDS, searchText),50);
+ }
+ };
+
+ private final Comparator friendSearchComparator = new Comparator() {
+ @Override
+ public int compare(ItemModel lhs, ItemModel rhs) {
+ final String lhsUserName = lhs.primaryPinyin;
+ final String rhsUserName = rhs.primaryPinyin;
+ final String lowerCaseKeyword = mSearchText.toLowerCase();
+ int result = lhsUserName.toLowerCase().indexOf(lowerCaseKeyword) - rhsUserName.toLowerCase().indexOf(lowerCaseKeyword);
+ if(result == 0){
+ return lhsUserName.compareTo(rhsUserName);
+ }else{
+ return result;
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setActionBarTitle("选择@好友");
+ requestFriendsList();
+ mBackgroundThread = new HandlerThread("Work Thread On SelectUserActivity");
+ mBackgroundThread.start();
+ mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper());
+ mkjBitmap = new KJBitmap();
+ mSelectUserPicSize = getResources().getDimensionPixelSize(R.dimen.select_user_pic_size);
+ }
+
+ @Override
+ protected boolean hasBackButton() {
+ return true;
+ }
+
+ @Override
+ protected void initActionBar(ActionBar actionBar) {
+ super.initActionBar(actionBar);
+ actionBar.setDisplayOptions(actionBar.getDisplayOptions() | ActionBar.DISPLAY_SHOW_CUSTOM);
+ mTvConfirm = new TextView(actionBar.getThemedContext());
+ ActionBar.LayoutParams params = new ActionBar.LayoutParams( ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT,
+ Gravity.END | Gravity.CENTER_VERTICAL);
+ mTvConfirm.setLayoutParams(params);
+ mTvConfirm.setText("确定");
+ mTvConfirm.setTextColor(getResources().getColor(R.color.white));
+ mTvConfirm.setOnClickListener(this);
+ actionBar.setCustomView(mTvConfirm);
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_select_user;
+ }
+
+ @Override
+ public void initData() {
+ mMinEtSearchWidth = getResources().getDimensionPixelSize(R.dimen.min_et_search_friend_width);
+ }
+
+ @Override
+ public void initView() {
+ mEmptyLayout = (EmptyLayout)findViewById(R.id.error_layout);
+ mLvFriends = (ListView)findViewById(R.id.search_result_listview);
+ mLayoutSearchBar = (RelativeLayout)findViewById(R.id.search_bar);
+ mEtSearch = (InputConnectEditText)findViewById(R.id.search_edit_text);
+ mEtSearch.addTextChangedListener(mSearchTextWatcher);
+ mAlphaBar = (AlphabetBar)findViewById(R.id.alphabet_bar);
+ mTvAlphaTip = (TextView)findViewById(R.id.alphabet_tip);
+ mIgSelectedUsers = (ImageGrid)findViewById(R.id.selected_users_ig);
+ mHsvSelectedUsers = (HorizontalScrollView)findViewById(R.id.selected_users_sv);
+ mLvFriends.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if(scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL){
+ hideInputMethod();
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ }
+ });
+ mLvFriends.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ ItemModel m = mShowMdlFriends.get(position);
+ if (m.type == ItemModel.TYPE_USER_INFO) {
+ Friend f = (Friend) m.data;
+ if (mSelectedFriends.contains(f)) {
+ mSelectedFriends.remove(f);
+ } else if (!checkIfExceedMaxSelectCount()) {
+ mSelectedFriends.add(f);
+ }
+ mLvAdapter.refleshItemStatus(position, f);
+ refleshSelectUsers();
+ }
+ }
+ });
+
+ mIgSelectedUsers.setOnImageGridItemClickListener(new ImageGrid.OnImageGridItemClickListener() {
+ @Override
+ public void onImageClick(int position) {
+ removeFriend(position);
+ }
+ });
+
+ mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING);
+ requestFriendsList();
+ }
+ });
+
+ mEtSearch.setOnKeyListener(new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+
+ return false;
+ }
+ });
+
+ mEtSearch.setOnSoftKeyListener(new InputConnectEditText.OnSoftKeyListener() {
+ @Override
+ public boolean onKey(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_DEL && mEtSearch.getText().length() == 0) {
+ int selectSize = mSelectedFriends.size();
+ if (selectSize != 0) {
+ removeFriend(selectSize - 1);
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ }
+
+ private void removeFriend(int position){
+ Friend removeFriend = null;
+ final int removeIndex = position;
+ int i = 0;
+ for (Friend f : mSelectedFriends) {
+ if (i == removeIndex) {
+ removeFriend = f;
+ break;
+ }
+ ++i;
+ }
+ if (removeFriend != null) {
+ mSelectedFriends.remove(removeFriend);
+ mLvAdapter.notifyDataSetChanged();
+ refleshSelectUsers();
+ }
+ }
+
+ private void refleshSelectUsers(){
+ int selectFriendsSize = mSelectedFriends.size();
+ mTvConfirm.setText(String.format("确定(%s/%s)", selectFriendsSize, MAX_SELECT_USER_COUNT));
+ if(selectFriendsSize == 0){
+ mIgSelectedUsers.setVisibility(View.GONE);
+ }else {
+ mIgSelectedUsers.setVisibility(View.VISIBLE);
+ Bitmap[] userBitmaps = new Bitmap[mSelectedFriends.size()];
+ int i = 0;
+ for(Friend f : mSelectedFriends){
+ userBitmaps[i++] = getCacheUserBitmap(f.getPortrait());
+ }
+ mIgSelectedUsers.setBitmaps(userBitmaps);
+ }
+ mUIHandler.sendEmptyMessageDelayed(MSG_REFLESH_SEARCH_BAR,200);
+ }
+
+ private boolean checkIfExceedMaxSelectCount(){
+ boolean isExceed = mSelectedFriends.size() >= MAX_SELECT_USER_COUNT;
+ if(isExceed){
+ Toast.makeText(this,String.format("选择用户数不能超过%s个",MAX_SELECT_USER_COUNT),Toast.LENGTH_SHORT).show();
+ }
+ return isExceed;
+ }
+
+ private Bitmap getCacheUserBitmap(String url){
+ if(TextUtils.isEmpty(url))
+ return BitmapFactory.decodeResource(getResources(), R.drawable.widget_dface);
+
+ Bitmap bm = mkjBitmap.getMemoryCache(url);
+ if(bm == null){
+ byte[] bmBytes = mkjBitmap.getCache(url);
+ if(bmBytes != null){
+ bm = BitmapFactory.decodeStream(new ByteArrayInputStream(bmBytes));
+ }else{
+ bm = BitmapFactory.decodeResource(getResources(), R.drawable.widget_dface);
+ }
+ }
+
+ return bm;
+ }
+
+ @Override
+ public void onClick(View v) {
+ StringBuilder atUsers = new StringBuilder();
+ for(Friend f : mSelectedFriends){
+ atUsers.append(String.format("@%s ", f.getName()));
+ }
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra(RESULT_AT_USERS,atUsers.toString());
+ setResult(RESULT_OK, resultIntent);
+ finish();
+ }
+
+ private void initAlphaBar(){
+ mAlphaBar.setVisibility(View.VISIBLE);
+ mAlphaBar.setOnAlphabetTouchListener(new AlphabetBar.OnAlphabetTouchListener() {
+ @Override
+ public void onTouchMove(char al) {
+ FriendIndex friendIndex;
+ if (al == AlphabetBar.OTHER_CHARACTOR) {
+ friendIndex = mFriendIndexs.get(OTHER_INDEX_CHAR);
+ } else {
+ friendIndex = mFriendIndexs.get(al);
+ }
+ mTvAlphaTip.setVisibility(View.VISIBLE);
+ mTvAlphaTip.setText(String.valueOf(al));
+ if (friendIndex != null) {
+ mLvFriends.setSelection(friendIndex.position);
+ }
+ }
+
+ @Override
+ public void onTouchCancel() {
+ mTvAlphaTip.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ private void requestFriendsList(){
+ AppContext appContext = (AppContext) getApplication();
+ OSChinaApi.getAllFriendList(appContext.getLoginUid(), mFriendsListHandler);
+ }
+
+ private void clearHighLightTag(){
+ for(ItemModel model : mShowMdlFriends){
+ if(model.data instanceof Friend){
+ Friend user = (Friend) model.data;
+ user.setName(user.getName().replaceAll("<.+?>", ""));
+ }
+ }
+ }
+
+ private void hideInputMethod() {
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mEtSearch.getWindowToken(), 0);
+ }
+
+ private class ItemModel{
+ static final int TYPE_CHARACTER = 0x0;
+ static final int TYPE_USER_INFO = 0x1;
+ int type;
+ Object data;
+ String primaryPinyin;
+ }
+
+ private class FriendListAdapter extends BaseAdapter{
+
+ Activity activity;
+
+ SparseArray selectorHolder;
+
+ FriendListAdapter(){
+ this.activity = SelectUserActivity.this;
+ selectorHolder = new SparseArray<>();
+ }
+
+ @Override
+ public int getCount() {
+ return mShowMdlFriends == null ? 0 : mShowMdlFriends.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mShowMdlFriends.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mShowMdlFriends.get(position).type;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final int itemType = getItemViewType(position);
+ final ItemModel model = mShowMdlFriends.get(position);
+ if(itemType == ItemModel.TYPE_CHARACTER){
+ return getAlphaTitleView(model,convertView);
+ }else{
+ return getItemView(position,model,convertView);
+ }
+ }
+
+ private View getAlphaTitleView(final ItemModel model,View convertView){
+ TextView tvletter;
+ if(convertView == null){
+ tvletter = (TextView) View.inflate(activity, R.layout.item_letter_index_title, null);
+ }else{
+ tvletter = (TextView)convertView;
+ }
+ Character letter = (Character)model.data;
+ if(letter == OTHER_INDEX_CHAR){
+ tvletter.setText("#");
+ }else{
+ tvletter.setText(String.valueOf(letter));
+ }
+ return tvletter;
+ }
+
+ private View getItemView(final int position, final ItemModel model,View convertView){
+ ItemViewHolder viewHodler;
+ if(convertView == null){
+ convertView = View.inflate(activity,R.layout.list_select_friend,null);
+ viewHodler = new ItemViewHolder();
+ viewHodler.ivUserAvator = (ImageView)convertView.findViewById(R.id.iv_user_avator);
+ viewHodler.tvUserName = (TextView)convertView.findViewById(R.id.tv_user_name);
+ viewHodler.ivSelector = (ImageView)convertView.findViewById(R.id.iv_selector);
+ convertView.setTag(viewHodler);
+ }else{
+ viewHodler = (ItemViewHolder)convertView.getTag();
+ }
+
+ Friend friend = (Friend)model.data;
+ viewHodler.tvUserName.setText(Html.fromHtml(friend.getName()));
+
+ selectorHolder.put(position,viewHodler.ivSelector);
+
+
+ if(mSelectedFriends.contains(friend)){
+ viewHodler.ivSelector.setImageResource(R.drawable.bg_cb_friend_selected);
+ }else{
+ viewHodler.ivSelector.setImageResource(R.drawable.bg_cb_friend_unselect);
+ }
+
+ mkjBitmap.display(viewHodler.ivUserAvator,friend.getPortrait());
+
+ return convertView;
+ }
+
+ void refleshItemStatus(int position, Friend friend){
+ ImageView ivSelector = selectorHolder.get(position);
+ if(ivSelector != null){
+ if(mSelectedFriends.contains(friend)){
+ ivSelector.setImageResource(R.drawable.bg_cb_friend_selected);
+ }else{
+ ivSelector.setImageResource(R.drawable.bg_cb_friend_unselect);
+ }
+ }
+ }
+
+ private class ItemViewHolder{
+ ImageView ivUserAvator;
+ TextView tvUserName;
+ ImageView ivSelector;
+ }
+ }
+
+ public class BackgroundHandler extends Handler {
+
+ public BackgroundHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if(msg.what == MSG_DATA_PROCESS){
+ FriendsList friendList = (FriendsList)msg.obj;
+ processData(friendList);
+ mUIHandler.sendEmptyMessage(MSG_DATA_PROCESS_SUCCESS);
+ }else if(msg.what == MSG_SEARCH_FRIENDS){
+ String searchText = (String)msg.obj;
+ if(mShowMdlFriends != null){
+ clearHighLightTag();
+ mShowMdlFriends.clear();
+ }
+
+ if(!TextUtils.isEmpty(searchText)){
+ searchFriends(searchText);
+ }else{
+ mShowMdlFriends.addAll(mAllMdlFriends);
+ }
+
+ mUIHandler.sendEmptyMessage(MSG_SEARCH_FRIENDS_SUCCESS);
+ }
+ }
+
+ private void processData(FriendsList friendList){
+ if(friendList != null) {
+
+ List allFriends = friendList.getFriendlist();
+ if (allFriends == null || allFriends.isEmpty()) {
+ // no friends
+ return;
+ }
+
+ mTotalFriendSize = allFriends.size();
+ mFriendIndexs = new TreeMap<>();
+ Character firstAlpha;
+
+ for (Friend f : allFriends) {
+ firstAlpha = StringUtils.getFirstLetter(f.getName().charAt(0));
+ if ('A' <= firstAlpha && firstAlpha <= 'Z') {
+ addUserIndex(firstAlpha, f);
+ } else {
+ addUserIndex(OTHER_INDEX_CHAR, f);
+ }
+ }
+
+ mShowMdlFriends = new ArrayList<>();
+
+ ItemModel model;
+ FriendIndex friendIndex;
+ int itemPos = 0;
+ for (java.util.Map.Entry entry : mFriendIndexs.entrySet()) {
+ model = new ItemModel();
+ model.type = ItemModel.TYPE_CHARACTER;
+ model.data = entry.getKey();
+ mShowMdlFriends.add(model);
+ friendIndex = entry.getValue();
+ friendIndex.position = itemPos;
+ ++itemPos;
+ for (Friend friend : friendIndex.friends) {
+ model = new ItemModel();
+ model.type = ItemModel.TYPE_USER_INFO;
+ model.data = friend;
+ model.primaryPinyin = StringUtils.toPrimaryPinyin(friend.getName());
+ mShowMdlFriends.add(model);
+ ++itemPos;
+ }
+ }
+
+ mAllMdlFriends = new ArrayList<>();
+ mAllMdlFriends.addAll(mShowMdlFriends);
+ }
+ }
+
+ private void addUserIndex(char letter, Friend friend){
+ FriendIndex friendIndex = mFriendIndexs.get(letter);
+ if(friendIndex == null){
+ friendIndex = new FriendIndex();
+ mFriendIndexs.put(letter, friendIndex);
+ }
+ friendIndex.friends.add(friend);
+ }
+
+ private void searchFriends(String searchText){
+ if(mAllMdlFriends == null || mAllMdlFriends.isEmpty())
+ return;
+
+ for(ItemModel m : mAllMdlFriends){
+ if(m.type == ItemModel.TYPE_USER_INFO && m.data instanceof Friend){
+ Friend f = (Friend) m.data;
+ String name = f.getName();
+ String lowerSearchText = searchText.toLowerCase();
+
+ if(name.toLowerCase().contains(lowerSearchText)){
+ f.setName(name.replaceAll("((?i)" + searchText + ")",HIGHLIGHT_REPLACEMENT));
+ mShowMdlFriends.add(m);
+ continue;
+ }
+
+ String lowerCasePrimaryPinyin = m.primaryPinyin.toLowerCase();
+ if(lowerCasePrimaryPinyin.contains(lowerSearchText)){
+ String highlightName = highlightName(f.getName(),lowerSearchText, lowerCasePrimaryPinyin);
+ f.setName(highlightName);
+ mShowMdlFriends.add(m);
+ }
+ }
+ }
+
+ Collections.sort(mShowMdlFriends, friendSearchComparator);
+ }
+
+ private String highlightName(String name , String keyWord , String primaryPinyin){
+ if(name == null || primaryPinyin == null || name.length() != primaryPinyin.length())
+ return name;
+
+ int startIndex, endIndex;
+
+ StringBuilder srcBuilder = new StringBuilder(name);
+ StringBuilder primaryPinyinBuidler = new StringBuilder(primaryPinyin);
+
+ startIndex = primaryPinyin.indexOf(keyWord);
+ String beginTag = String.format("", HIGHLIGHT_COLOR);
+ String endTag = "";
+ while(startIndex != -1){
+ srcBuilder.insert(startIndex, beginTag);
+ primaryPinyinBuidler.insert(startIndex, beginTag);
+ endIndex = startIndex + keyWord.length() + beginTag.length();
+ srcBuilder.insert(endIndex, endTag);
+ primaryPinyinBuidler.insert(endIndex, endTag);
+ startIndex = primaryPinyinBuidler.indexOf(keyWord, endIndex + endTag.length());
+ }
+
+ return srcBuilder.toString();
+ }
+ }
+
+ private class FriendIndex{
+ int position;
+ List friends = new ArrayList<>();
+ }
+}
diff --git a/app/src/main/java/net/oschina/app/util/StringUtils.java b/app/src/main/java/net/oschina/app/util/StringUtils.java
index 7a56fcbc4360b23e3205d2f7bbaf255e20e2ded8..7d6b9fa8e1ab14ab388c36f4de41021707f79ea3 100644
--- a/app/src/main/java/net/oschina/app/util/StringUtils.java
+++ b/app/src/main/java/net/oschina/app/util/StringUtils.java
@@ -1,5 +1,9 @@
package net.oschina.app.util;
+import net.sourceforge.pinyin4j.PinyinHelper;
+import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
+import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -476,4 +480,43 @@ public class StringUtils {
return df.format(new Date());
}
+ private static final HanyuPinyinOutputFormat PINGYIN_FORMAT = new HanyuPinyinOutputFormat();
+ /**
+ * 获取一个汉字的拼音首字母。 (大写)
+ *
+ * @return 如果是汉字则返回首字母,否则直接返回
+ */
+ public static Character getFirstLetter(char ch) {
+
+ if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z')) {
+ return Character.toUpperCase(ch);
+ }
+
+ try {
+ String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(ch, PINGYIN_FORMAT);
+ if(pinyins == null || pinyins.length == 0){
+ return ch;
+ }else{
+ return Character.toUpperCase(pinyins[0].charAt(0));
+ }
+ } catch (BadHanyuPinyinOutputFormatCombination e) {
+ e.printStackTrace();
+ }
+
+ return ch;
+ }
+
+ /**
+ * 返回首字母组成的字符串,英文(统一大写)和特殊字符不变化
+ * @param str
+ * @return
+ */
+ public static String toPrimaryPinyin(String str){
+ char[] strChars = str.toCharArray();
+ StringBuilder primaryPinyin = new StringBuilder();
+ for(char ch : strChars){
+ primaryPinyin.append(getFirstLetter(ch));
+ }
+ return primaryPinyin.toString();
+ }
}
diff --git a/app/src/main/java/net/oschina/app/widget/AlphabetBar.java b/app/src/main/java/net/oschina/app/widget/AlphabetBar.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8dbc16f6e820bc63ba4f69a101e844a42e95a93
--- /dev/null
+++ b/app/src/main/java/net/oschina/app/widget/AlphabetBar.java
@@ -0,0 +1,137 @@
+package net.oschina.app.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint.FontMetricsInt;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import net.oschina.app.R;
+
+/**
+ *
+ * @author Darcy www.darcye.com
+ *
+ */
+public class AlphabetBar extends View {
+
+ public static final char OTHER_CHARACTOR = '#';
+
+ private static final char[] ALPHABETS = new char[27];
+
+ private TextPaint mPaint;
+
+ private int mUnitHeight;
+ private int letterYOffset;
+ private boolean mHasInitParams = false;
+
+ private OnAlphabetTouchListener mOnAlphabetTouchListener;
+
+ private OnSizeChangedListener mOnSizeChangedListener;
+
+ static{
+ for(char ch = 'A'; ch <= 'Z' ; ++ch){
+ ALPHABETS[ch - 'A'] = ch;
+ }
+ ALPHABETS[26] = OTHER_CHARACTOR;
+ }
+
+ public AlphabetBar(Context context){
+ this(context,null);
+ }
+
+ public AlphabetBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AlphabetBar);
+ final int textSize = a.getDimensionPixelSize(R.styleable.AlphabetBar_alphaTextSize, 12);
+ final int textColor = a.getColor(R.styleable.AlphabetBar_alphaTextColor, 0xFF000000);
+ mPaint = new TextPaint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTextSize(textSize);
+ mPaint.setColor(textColor);
+ a.recycle();
+ setClickable(true);
+ }
+
+ public void setOnAlphabetTouchListener(OnAlphabetTouchListener l) {
+ this.mOnAlphabetTouchListener = l;
+ }
+
+ public void setOnSizeChangedListener(
+ OnSizeChangedListener l) {
+ this.mOnSizeChangedListener = l;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ final int width = getWidth();
+ if(!mHasInitParams){
+ final int paddingTop = getPaddingTop();
+ final int paddingBottom = getPaddingBottom();
+ final int height = getHeight() - paddingTop - paddingBottom;
+ this.mUnitHeight = height / ALPHABETS.length;
+ final FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
+ this.letterYOffset = (int) ((mUnitHeight - (fontMetrics.bottom - fontMetrics.top)) * 0.5 - fontMetrics.top + paddingTop);
+ mHasInitParams = false;
+ }
+
+ int letterXOffset;
+ String letter;
+ for(int i = 0 ; i < ALPHABETS.length ; ++i){
+ letter = String.valueOf(ALPHABETS[i]);
+ letterXOffset = (int) ((width - mPaint.measureText(letter)) * 0.5);
+ canvas.drawText(letter, letterXOffset, letterYOffset + mUnitHeight * i, mPaint);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ super.dispatchTouchEvent(event);
+ return true;
+ }
+
+ private int originIndex = -1;
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+ final int action = event.getAction();
+ final int paddingTop = getPaddingTop();
+ if(action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE){
+ final float eventY = event.getY();
+ final int newIndex = (int) ((eventY - paddingTop) / mUnitHeight) ;
+ if(originIndex != newIndex && 0 <= newIndex && newIndex < ALPHABETS.length){
+ originIndex = newIndex;
+ if(mOnAlphabetTouchListener != null){
+ mOnAlphabetTouchListener.onTouchMove(ALPHABETS[newIndex]);
+ }
+ }
+ }else if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL){
+ originIndex = -1;
+ if(mOnAlphabetTouchListener != null)
+ mOnAlphabetTouchListener.onTouchCancel();
+ }
+ return true;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if(mOnSizeChangedListener != null)
+ mOnSizeChangedListener.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ public interface OnAlphabetTouchListener{
+ void onTouchMove(char al);
+
+ void onTouchCancel();
+ }
+
+ public interface OnSizeChangedListener{
+ void onSizeChanged(int w, int h, int oldw, int oldh);
+ }
+}
diff --git a/app/src/main/java/net/oschina/app/widget/ImageGrid.java b/app/src/main/java/net/oschina/app/widget/ImageGrid.java
new file mode 100644
index 0000000000000000000000000000000000000000..b41400c3b9b096f0ec8beb0430504fa0f27018d6
--- /dev/null
+++ b/app/src/main/java/net/oschina/app/widget/ImageGrid.java
@@ -0,0 +1,150 @@
+package net.oschina.app.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import net.oschina.app.R;
+
+/**
+ *
+ * 图片表格控件,它会根据占用的width和规定的列数来平均每张图片的大小。
+ *
+ * @author Darcy www.darcye.com
+ *
+ */
+public class ImageGrid extends View {
+
+ private int mImageNumber;
+
+ private Bitmap[] mBitmaps;
+
+ private SparseArray mPositionRectMap;
+ private ViewConfiguration mViewConfiguration;
+
+ private OnImageGridItemClickListener mOnImageGridItemClickListener;
+
+ private int mImageSize;
+ private int mImageGap;
+
+ public ImageGrid(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageGrid);
+ mImageGap = a.getDimensionPixelSize(R.styleable.ImageGrid_ImageGap, 5);
+ mImageSize = a.getDimensionPixelSize(R.styleable.ImageGrid_ImageSize, 15);
+ a.recycle();
+ mViewConfiguration = ViewConfiguration.get(context);
+ }
+
+ public void setOnImageGridItemClickListener(
+ OnImageGridItemClickListener onImageGridItemClickListener) {
+ mOnImageGridItemClickListener = onImageGridItemClickListener;
+ }
+
+ /**
+ * 设置
+ * @param bitmaps
+ */
+ public void setBitmaps(Bitmap[] bitmaps){
+ this.mBitmaps = bitmaps;
+ this.mImageNumber = bitmaps.length;
+ this.mPositionRectMap = new SparseArray<>(bitmaps.length);
+ invalidate();
+ requestLayout();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ if(mImageNumber <= 0){
+ return;
+ }
+
+ Rect rect;
+ int curLeft = 0 ;
+ for(int imagePos = 0; imagePos < mImageNumber ; ++imagePos ){
+ rect = mPositionRectMap.get(imagePos);
+ if(rect == null){
+ rect = new Rect();
+ rect.left = curLeft;
+ rect.top = 0;
+ rect.right = curLeft + mImageSize;
+ rect.bottom = mImageSize;
+ mPositionRectMap.put(imagePos, rect);
+ }
+ canvas.drawBitmap(mBitmaps[imagePos], null, rect, null);
+ curLeft += mImageSize + mImageGap;
+ }
+ }
+
+
+ private float touchDownX, touchDownY;
+ private boolean isValidClick;
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ switch(event.getAction()){
+ case MotionEvent.ACTION_DOWN:
+ touchDownX = event.getX();
+ touchDownY = event.getY();
+ isValidClick = true;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if(!isValidClick && (Math.abs(touchDownY - event.getY()) > mViewConfiguration.getScaledTouchSlop()
+ || Math.abs(touchDownX - event.getX()) > mViewConfiguration.getScaledTouchSlop())){
+ isValidClick = false;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ isValidClick = false;
+ break;
+ case MotionEvent.ACTION_UP:
+ if(isValidClick){
+ final int width = getWidth();
+ final int validItemWidth = width / mImageNumber;
+ int clickPos = (int) Math.ceil(touchDownX / validItemWidth);
+ if(mOnImageGridItemClickListener != null && clickPos >= 1){
+ mOnImageGridItemClickListener.onImageClick(clickPos-1);
+ }
+ }
+ isValidClick = false;
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ if(mImageNumber <= 0){
+ setMeasuredDimension(0, 0);
+ return;
+ }
+
+ final int specWidthSize;
+ if(mImageNumber == 1){
+ specWidthSize = mImageSize;
+ }else{
+ specWidthSize = (mImageSize * mImageNumber) + mImageGap * (mImageNumber - 1);
+ }
+
+ int specHeightSize = mImageSize;
+
+ setMeasuredDimension(specWidthSize, specHeightSize);
+ }
+
+
+ public interface OnImageGridItemClickListener{
+ /**
+ * call back when clicking image
+ *
+ */
+ void onImageClick(int position);
+ }
+}
diff --git a/app/src/main/java/net/oschina/app/widget/InputConnectEditText.java b/app/src/main/java/net/oschina/app/widget/InputConnectEditText.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a5fe0bd105ec6716b66f91cd5888dd74faf883b
--- /dev/null
+++ b/app/src/main/java/net/oschina/app/widget/InputConnectEditText.java
@@ -0,0 +1,52 @@
+package net.oschina.app.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.widget.EditText;
+
+/**
+ * Created by Darcy on 2015/8/28.
+ */
+public class InputConnectEditText extends EditText{
+
+ private OnSoftKeyListener mOnSoftKeyListener;
+
+ public InputConnectEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setOnSoftKeyListener(OnSoftKeyListener l){
+ this.mOnSoftKeyListener = l;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return new ZanyInputConnection(super.onCreateInputConnection(outAttrs),
+ true);
+ }
+
+ private class ZanyInputConnection extends InputConnectionWrapper {
+
+ public ZanyInputConnection(InputConnection target, boolean mutable) {
+ super(target, mutable);
+ }
+
+ @Override
+ public boolean sendKeyEvent(KeyEvent event) {
+ if(mOnSoftKeyListener != null){
+ if(mOnSoftKeyListener.onKey(event)){
+ return true;
+ }
+ }
+ return super.sendKeyEvent(event);
+ }
+ }
+
+ public interface OnSoftKeyListener{
+ boolean onKey(KeyEvent event);
+ }
+}
diff --git a/app/src/main/res/drawable-xhdpi/bg_edit_gray.9.png b/app/src/main/res/drawable-xhdpi/bg_edit_gray.9.png
new file mode 100644
index 0000000000000000000000000000000000000000..79388d5b401b5cb7c0e1319ace20538e47dd1859
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_edit_gray.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/cb_circle_normal.png b/app/src/main/res/drawable-xhdpi/cb_circle_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf886cc9e121187e14d0d1423d9bc89b793b840f
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/cb_circle_normal.png differ
diff --git a/app/src/main/res/drawable-xhdpi/cb_circle_selected.png b/app/src/main/res/drawable-xhdpi/cb_circle_selected.png
new file mode 100644
index 0000000000000000000000000000000000000000..d8d1732b0b33fbed6cf46dce6935225d099a29eb
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/cb_circle_selected.png differ
diff --git a/app/src/main/res/drawable/bg_alphabet_bar.xml b/app/src/main/res/drawable/bg_alphabet_bar.xml
new file mode 100644
index 0000000000000000000000000000000000000000..802b26641ee955704d903c8bf7e1b0e2c2fd2101
--- /dev/null
+++ b/app/src/main/res/drawable/bg_alphabet_bar.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_alphabet_tip.xml b/app/src/main/res/drawable/bg_alphabet_tip.xml
new file mode 100644
index 0000000000000000000000000000000000000000..aa3929422bf95a79e19bf0e9f2a4a2254d189097
--- /dev/null
+++ b/app/src/main/res/drawable/bg_alphabet_tip.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_cb_friend_selected.xml b/app/src/main/res/drawable/bg_cb_friend_selected.xml
new file mode 100644
index 0000000000000000000000000000000000000000..06aeabf1523a72cc684f2b39459cd8906c20939d
--- /dev/null
+++ b/app/src/main/res/drawable/bg_cb_friend_selected.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_cb_friend_unselect.xml b/app/src/main/res/drawable/bg_cb_friend_unselect.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c4b427a00415bd48374e1900acfda6655ef23cbd
--- /dev/null
+++ b/app/src/main/res/drawable/bg_cb_friend_unselect.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_select_user.xml b/app/src/main/res/layout/activity_select_user.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cbb6fcc490645bb707fb74bcafe9dc8fcf4aa7af
--- /dev/null
+++ b/app/src/main/res/layout/activity_select_user.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_letter_index_title.xml b/app/src/main/res/layout/item_letter_index_title.xml
new file mode 100644
index 0000000000000000000000000000000000000000..17dba942efca84dc25dc9c77654896ee9c518f8d
--- /dev/null
+++ b/app/src/main/res/layout/item_letter_index_title.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_select_friend.xml b/app/src/main/res/layout/list_select_friend.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2b6f7f0a90701bfed5ffbd762f79bc2c40f033c7
--- /dev/null
+++ b/app/src/main/res/layout/list_select_friend.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 01c26a29d994524dbda2f32f163a230183acd8c7..52c31a037a6982ff61e4c70f9d76cff8f8eded79 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -10,5 +10,14 @@
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 4df8e5e3f0172a373d867a0655cee1401007e561..1599f0125ffa75b814055ed05a79417fb96da3f5 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -90,4 +90,6 @@
#464646
#6baf77
+
+ #338899a6
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 58d7807c6b529f9183f5a957c82c77a49da4ba5d..35643f0fb464ee6b22e6d5943015f72486c49d66 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -151,4 +151,7 @@
- 0.14
- 0.11
+ 12dp
+ 20dp
+ 100dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eea0b4c9d0bd5bd5511b4404c569c7c4c91e2e3c..46b26bd943e8daf1075656bdd1f7ad2749e2e40e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -303,4 +303,8 @@
选择团队
我的任务
+ SelectUserActivity
+
+ Hello world!
+ Settings
diff --git a/gradle.properties b/gradle.properties
index a67972e42e8d5049d34bf26d17dc1643e814d202..9b73b9663e23405dae1fb37d6ce1b0d146564003 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,5 +16,8 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+org.gradle.daemon=true
+org.gradle.parallel=true
+
KEYSTORE_PASSWORD=oschina123
KEY_PASSWORD=oschina123
\ No newline at end of file