From 46324e09d8d13e83480b9654563d618db8701cf3 Mon Sep 17 00:00:00 2001 From: Alex Burka Date: Fri, 22 Nov 2013 01:28:55 -0500 Subject: [PATCH] friend tagging feature Conflicts: Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/FbWrapper.java --- .../fbwrapper/AccessTokenDialogFragment.java | 93 ++++++++ .../com/danvelazco/fbwrapper/FbWrapper.java | 31 +++ .../fbwrapper/TagFriendDialogFragment.java | 199 ++++++++++++++++++ .../activity/BaseFacebookWebViewActivity.java | 17 +- .../preferences/FacebookPreferences.java | 42 ++++ .../layout/access_token_fragment_dialog.xml | 12 ++ .../src/main/res/layout/drawer_menu.xml | 31 +++ .../res/layout/simple_black_list_item.xml | 9 + .../res/layout/tag_friend_fragment_dialog.xml | 24 +++ .../src/main/res/values/strings.xml | 11 +- .../src/main/res/xml/main_preferences.xml | 6 + 11 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/AccessTokenDialogFragment.java create mode 100644 Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/TagFriendDialogFragment.java create mode 100644 Tinfoil-for-Facebook/src/main/res/layout/access_token_fragment_dialog.xml create mode 100644 Tinfoil-for-Facebook/src/main/res/layout/simple_black_list_item.xml create mode 100644 Tinfoil-for-Facebook/src/main/res/layout/tag_friend_fragment_dialog.xml diff --git a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/AccessTokenDialogFragment.java b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/AccessTokenDialogFragment.java new file mode 100644 index 0000000..dfb7054 --- /dev/null +++ b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/AccessTokenDialogFragment.java @@ -0,0 +1,93 @@ +package com.danvelazco.fbwrapper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.danvelazco.fbwrapper.activity.BaseFacebookWebViewActivity; +import com.danvelazco.fbwrapper.preferences.FacebookPreferences; + +import android.app.DialogFragment; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup; +import android.view.Window; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.Toast; + +public class AccessTokenDialogFragment extends DialogFragment { + + private WebView mWebView; + + public static AccessTokenDialogFragment newInstance() { + AccessTokenDialogFragment f = new AccessTokenDialogFragment(); + f.setArguments(new Bundle()); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View v = inflater.inflate(R.layout.access_token_fragment_dialog, container, false); + + int width = getResources().getDisplayMetrics().widthPixels; + int height = getResources().getDisplayMetrics().heightPixels; + v.findViewById(R.id.access_layout).setMinimumHeight((int) (height*0.7)); + v.findViewById(R.id.access_layout).setMinimumWidth((int) (width*0.9)); + getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); + + mWebView = (WebView)v.findViewById(R.id.access_webview); + mWebView.setWebViewClient(new WebViewClient() { + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url.startsWith("https://alexburka.com/tinfoil")) { + ((FacebookPreferences)getActivity()).received_access_token(url.split("=")[1].split("&")[0]); + dismiss(); + return true; + } + return false; + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + Toast.makeText(getActivity(), "Internet error: " + description, Toast.LENGTH_SHORT).show(); + dismiss(); + } + }); + mWebView.loadUrl("https://graph.facebook.com/oauth/authorize?type=user_agent&client_id=266257470187937&redirect_uri=https%3A%2F%2Falexburka.com%2Ftinfoil&scope=user_friends&offline_access=true"); + + return v; + } + +} diff --git a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/FbWrapper.java b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/FbWrapper.java index 617b202..5a290a0 100644 --- a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/FbWrapper.java +++ b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/FbWrapper.java @@ -1,6 +1,9 @@ package com.danvelazco.fbwrapper; import android.app.AlertDialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -8,6 +11,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; @@ -136,6 +140,7 @@ private void setOnClickListeners() { findViewById(R.id.menu_item_newsfeed).setOnClickListener(buttonsListener); findViewById(R.id.menu_items_notifications).setOnClickListener(buttonsListener); findViewById(R.id.menu_item_messages).setOnClickListener(buttonsListener); + findViewById(R.id.menu_item_tag).setOnClickListener(buttonsListener); findViewById(R.id.menu_share_this).setOnClickListener(buttonsListener); findViewById(R.id.menu_preferences).setOnClickListener(buttonsListener); findViewById(R.id.menu_about).setOnClickListener(buttonsListener); @@ -327,6 +332,9 @@ public void onClick(View v) { case R.id.menu_item_messages: loadNewPage(mDomainToUse + URL_PAGE_MESSAGES); break; + case R.id.menu_item_tag: + tagFriend(); + break; case R.id.menu_share_this: shareCurrentPage(); break; @@ -363,6 +371,29 @@ public void onClick(DialogInterface dialog, int which) { }); alertDialog.show(); } + + /** + * Search for a friend and insert tagging syntax. + */ + private void tagFriend() { + FragmentTransaction ft = getFragmentManager().beginTransaction(); + Fragment prev = getFragmentManager().findFragmentByTag("tag_dialog"); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + // Create and show the dialog. + DialogFragment newFragment = TagFriendDialogFragment.newInstance(); + newFragment.show(ft, "tag_dialog"); + } + + public void friendTagged(String text) { + // send text to WebView + for (int i = 0; i < text.length(); ++i) { + mWebView.dispatchKeyEvent(new KeyEvent(100, text.substring(i, i+1), 1, 0)); + } + } /** * {@inheritDoc} diff --git a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/TagFriendDialogFragment.java b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/TagFriendDialogFragment.java new file mode 100644 index 0000000..71dc08c --- /dev/null +++ b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/TagFriendDialogFragment.java @@ -0,0 +1,199 @@ +package com.danvelazco.fbwrapper; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.danvelazco.fbwrapper.activity.BaseFacebookWebViewActivity; + +import android.app.DialogFragment; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.WindowManager.LayoutParams; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +public class TagFriendDialogFragment extends DialogFragment { + + private class GraphSearchTask extends AsyncTask { + + @Override + protected void onPreExecute() { + mID = ID_LOADING; + tag_list.setAdapter( new ArrayAdapter(getActivity().getApplicationContext(), R.layout.simple_black_list_item, new String[]{"..."})); + } + + @Override + protected String doInBackground(String... names) { + String name = names[0]; + + HttpClient client = new DefaultHttpClient(); + String json = ""; + try { + String line = ""; + Log.d("TFD", BaseFacebookWebViewActivity.URL_GRAPH_SEARCH(getActivity(), name)); + HttpGet request = new HttpGet(BaseFacebookWebViewActivity.URL_GRAPH_SEARCH(getActivity(), name)); + HttpResponse response = client.execute(request); + BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); + while ((line = rd.readLine()) != null) { + json += line + System.getProperty("line.separator"); + } + } catch (IllegalArgumentException e1) { + e1.printStackTrace(); + } catch (IOException e2) { + e2.printStackTrace(); + } + return json; + } + + @Override + protected void onPostExecute(String json) { + Log.d("TFD", json); + + mIDs.clear(); + mFulls.clear(); + mFirsts.clear(); + mLasts.clear(); + try { + JSONArray peeps = new JSONObject(json).getJSONArray("data"); + for (int i = 0; i < peeps.length(); ++i) { + mIDs.add(peeps.getJSONObject(i).getString("uid")); + mFulls.add(peeps.getJSONObject(i).getString("name")); + mFirsts.add(peeps.getJSONObject(i).getString("first_name")); + mLasts.add(peeps.getJSONObject(i).getString("last_name")); + } + + tag_list.setAdapter(new ArrayAdapter(getActivity().getApplicationContext(), R.layout.simple_black_list_item, mFulls)); + mID = ID_CHOOSEFRIEND; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + + private View.OnClickListener mClickListener = null; + private TextWatcher mTextListener = null; + private GraphSearchTask mTask = null; + private static final int ID_LOADING = -1; + private static final int ID_CHOOSEFRIEND = -2; + private int mID = ID_LOADING; + private ArrayList mIDs, mFulls, mFirsts, mLasts; + private EditText tag_in; + private ListView tag_list; + + static TagFriendDialogFragment newInstance() { + TagFriendDialogFragment f = new TagFriendDialogFragment(); + f.setArguments(new Bundle()); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View v = inflater.inflate(R.layout.tag_friend_fragment_dialog, container, false); + tag_in = (EditText)v.findViewById(R.id.tag_in); + tag_list = (ListView)v.findViewById(R.id.tag_list); + + getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); + + // List view adapter + ((ListView)v.findViewById(R.id.tag_list)).setAdapter( new ArrayAdapter(getActivity().getApplicationContext(), R.layout.simple_black_list_item, new String[]{"enter name above"})); + + mIDs = new ArrayList(); + mFulls = new ArrayList(); + mFirsts = new ArrayList(); + mLasts = new ArrayList(); + + // listeners + mTextListener = new TextWatcher() { + + @Override + public void afterTextChanged(Editable s) { + // don't care + + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // don't care + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + if (s.length() > 0) { + if (mTask != null) mTask.cancel(true); + mTask = new GraphSearchTask(); + mTask.execute(s.toString()); + } + } + }; + + tag_in.addTextChangedListener(mTextListener); + tag_list.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (mID == ID_LOADING) { + return; + } + if (mID != ID_CHOOSEFRIEND) { + String name; + switch (position) { + case 0: + name = "0"; + break; + case 1: + name = mFirsts.get(mID); + break; + case 2: + name = mLasts.get(mID); + break; + default: + return; + } + String str = "@[" + mIDs.get(mID) + ":" + name + "]"; + + ((FbWrapper)getActivity()).friendTagged(str); + dismiss(); + } else { + mID = position; + + tag_list.setAdapter( new ArrayAdapter(getActivity().getApplicationContext(), R.layout.simple_black_list_item, new String[]{"tag as: " + mFulls.get(position), "tag as: " + mFirsts.get(position), "tag as: " + mLasts.get(position)})); + } + } + + }); + + return v; + } + +} diff --git a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/activity/BaseFacebookWebViewActivity.java b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/activity/BaseFacebookWebViewActivity.java index 0669781..6fd61da 100644 --- a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/activity/BaseFacebookWebViewActivity.java +++ b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/activity/BaseFacebookWebViewActivity.java @@ -30,6 +30,7 @@ import android.os.Build; import android.os.Bundle; import android.text.TextUtils; +import android.preference.PreferenceManager; import android.view.KeyEvent; import android.view.View; import android.webkit.CookieSyncManager; @@ -47,6 +48,8 @@ import com.danvelazco.fbwrapper.webview.FacebookWebViewClient; import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; /** * Base activity that uses a {@link FacebookWebView} to load the Facebook @@ -68,7 +71,19 @@ public abstract class BaseFacebookWebViewActivity extends Activity implements protected final static String INIT_URL_DESKTOP = "https://www.facebook.com"; protected final static String URL_PAGE_NOTIFICATIONS = "/notifications.php"; protected final static String URL_PAGE_MESSAGES = "/messages"; - + + protected final static String DEBUG_ACCESS_TOKEN = "CAACEdEose0cBAMDYT6fmTjrpgUPRmvx6k30geaNCtWYyEOy9hFw1ma4jq6yHmiJ55ZCJsMhQOrlrGyAK1WNI0ggp11ZBig7UGNZCwd7wxYkZASyXL3xEZBVncwwlvqnxGHk5eeZCSHp86dlcwD1lS5wNO9b6Go2P4TlU337IexAq4opg1qZAItmUOTgBLDgJJwQuXICprEm9gZDZD"; + public static String URL_GRAPH_SEARCH(Context ctx, String name) { + try { + String access_token = PreferenceManager.getDefaultSharedPreferences(ctx).getString("API_KEY", ""); + return "https://graph.facebook.com/fql?access_token=" + access_token + "&q=" + URLEncoder.encode("select uid, name, first_name, last_name from user where uid in (SELECT uid2 FROM friend WHERE uid1 = me()) and strpos(lower(name), '" + name.toLowerCase() + "')>=0 limit 10", "UTF-8"); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return ""; + } + } + // URL for Sharing Links // u = url & t = title protected final static String URL_PAGE_SHARE_LINKS = "/sharer.php?u=%s&t=%s"; diff --git a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/preferences/FacebookPreferences.java b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/preferences/FacebookPreferences.java index 6e90490..a801b26 100644 --- a/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/preferences/FacebookPreferences.java +++ b/Tinfoil-for-Facebook/src/main/java/com/danvelazco/fbwrapper/preferences/FacebookPreferences.java @@ -17,15 +17,25 @@ package com.danvelazco.fbwrapper.preferences; import android.app.AlertDialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentTransaction; import android.content.DialogInterface; import android.os.Bundle; import android.preference.EditTextPreference; +import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.support.v4.app.NavUtils; +import android.util.Log; import android.view.MenuItem; +import android.webkit.WebViewFragment; + +import com.danvelazco.fbwrapper.AccessTokenDialogFragment; import com.danvelazco.fbwrapper.R; +import com.danvelazco.fbwrapper.TagFriendDialogFragment; /** * Preferences activity @@ -49,6 +59,7 @@ public class FacebookPreferences extends PreferenceActivity { public final static String SITE_MODE_MOBILE = "mobile"; public final static String SITE_MODE_DESKTOP = "desktop"; public final static String ABOUT = "pref_about"; + public final static String API = "prefs_api"; // Preferences private EditTextPreference mPrefProxyHost = null; @@ -111,9 +122,40 @@ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, if (ABOUT.equals(key)) { showAboutAlert(); return true; + } else if (API.equals(key)) { + if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(API, false)) { + FragmentTransaction ft = getFragmentManager().beginTransaction(); + Fragment prev = getFragmentManager().findFragmentByTag("api_dialog"); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + // Create and show the dialog. + DialogFragment newFragment = AccessTokenDialogFragment.newInstance(); + newFragment.show(ft, "api_dialog"); + + // un-check the preference, until such time as the dialog manages to get an access token + ((CheckBoxPreference)findPreference("prefs_api")).setChecked(false); + } else { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .remove("API_KEY") + .commit(); + } + return true; } return false; } + + public void received_access_token(String token) { + Log.d("FP", "access token: " + token); + ((CheckBoxPreference)findPreference("prefs_api")).setChecked(true); + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putString("API_KEY", token) + .commit(); + } /** * Show an alert dialog with the information about the application. diff --git a/Tinfoil-for-Facebook/src/main/res/layout/access_token_fragment_dialog.xml b/Tinfoil-for-Facebook/src/main/res/layout/access_token_fragment_dialog.xml new file mode 100644 index 0000000..3dc204e --- /dev/null +++ b/Tinfoil-for-Facebook/src/main/res/layout/access_token_fragment_dialog.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/Tinfoil-for-Facebook/src/main/res/layout/drawer_menu.xml b/Tinfoil-for-Facebook/src/main/res/layout/drawer_menu.xml index 2d0e4f7..a2a4bb2 100644 --- a/Tinfoil-for-Facebook/src/main/res/layout/drawer_menu.xml +++ b/Tinfoil-for-Facebook/src/main/res/layout/drawer_menu.xml @@ -211,6 +211,37 @@ android:text="@string/menu_messages"/> + + + + + + + + + + \ No newline at end of file diff --git a/Tinfoil-for-Facebook/src/main/res/layout/tag_friend_fragment_dialog.xml b/Tinfoil-for-Facebook/src/main/res/layout/tag_friend_fragment_dialog.xml new file mode 100644 index 0000000..54cce14 --- /dev/null +++ b/Tinfoil-for-Facebook/src/main/res/layout/tag_friend_fragment_dialog.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/Tinfoil-for-Facebook/src/main/res/values/strings.xml b/Tinfoil-for-Facebook/src/main/res/values/strings.xml index 10860c8..aac6e1d 100644 --- a/Tinfoil-for-Facebook/src/main/res/values/strings.xml +++ b/Tinfoil-for-Facebook/src/main/res/values/strings.xml @@ -43,6 +43,7 @@ Refresh Notifications Messages + Tag friend Preferences About Kill @@ -64,6 +65,9 @@ Proxy host Proxy port + Use Facebook API features + If enabled, Tinfoil will request a Facebook API access token to provide features such as inline friend tagging. + Auto Force mobile site @@ -74,6 +78,11 @@ mobile desktop + + + Search friends + Tag will appear here + Tag About Written by Daniel Velazco. Click here for more information. @@ -102,4 +111,4 @@ \nThis app is for those users that require a Tinfoil Hat when logging in to Facebook. It creates a sandbox for facebook\'s mobile site. Requires INTERNET permissions, and if you are skeptic about it, feel free to check the source code listed above. Coarse location permissions are required but location is not used unless you turn \"Allow Check-ins\" in the preferences.\n \n - \ No newline at end of file + diff --git a/Tinfoil-for-Facebook/src/main/res/xml/main_preferences.xml b/Tinfoil-for-Facebook/src/main/res/xml/main_preferences.xml index f3b329b..86488e9 100644 --- a/Tinfoil-for-Facebook/src/main/res/xml/main_preferences.xml +++ b/Tinfoil-for-Facebook/src/main/res/xml/main_preferences.xml @@ -36,6 +36,12 @@ android:key="prefs_mobile_site" android:summary="@string/prefs_mobile_site_sry" android:title="@string/prefs_mobile_site" /> + +