├── .gitignore ├── LICENCE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── jakelane │ │ └── wrapperforfacebook │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── me │ │ │ └── jakelane │ │ │ └── wrapperforfacebook │ │ │ ├── CustomWebChromeClient.java │ │ │ ├── Helpers.java │ │ │ ├── JavaScriptHelpers.java │ │ │ ├── JavaScriptInterfaces.java │ │ │ ├── MainActivity.java │ │ │ ├── NotificationService.java │ │ │ ├── PollReceiver.java │ │ │ ├── SettingsActivity.java │ │ │ └── WebViewListener.java │ └── res │ │ ├── drawable │ │ ├── ic_fab_checkin.xml │ │ ├── ic_fab_menu.xml │ │ ├── ic_fab_photo.xml │ │ ├── ic_fab_text.xml │ │ ├── ic_menu_back.xml │ │ ├── ic_menu_fblogin.xml │ │ ├── ic_menu_forward.xml │ │ ├── ic_menu_friendreq.xml │ │ ├── ic_menu_jump_top.xml │ │ ├── ic_menu_mainmenu.xml │ │ ├── ic_menu_messages.xml │ │ ├── ic_menu_most_recent.xml │ │ ├── ic_menu_news.xml │ │ ├── ic_menu_notifications.xml │ │ ├── ic_menu_notifications_active.xml │ │ ├── ic_menu_refresh.xml │ │ ├── ic_menu_search.xml │ │ ├── ic_menu_settings.xml │ │ ├── ic_menu_top_stories.xml │ │ ├── notify_logo.xml │ │ ├── side_nav_bar.xml │ │ └── side_profile.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── app_bar_main.xml │ │ ├── content_main.xml │ │ ├── menu_badge_full.xml │ │ ├── nav_header_main.xml │ │ └── video_progress.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ └── main.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-es │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ └── strings.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── settings.xml │ └── test │ └── java │ └── me │ └── jakelane │ └── wrapperforfacebook │ └── ExampleUnitTest.java ├── assets ├── logo.svg └── logo_outline.svg ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Windows thumbnail db 19 | Thumbs.db 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 32 | .gradle 33 | build/ 34 | .navigation/ 35 | 36 | #NDK 37 | obj/ 38 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jake Lane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toffeed [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | 3 | Toffeed is a wrapper for Facebook's mobile site that provides enhancements to the normal browser experience. 4 | 5 | This repository is no longer maintained as I no longer have the time nor motivation for this project. There are a couple of forks around and [MaterialFBook by ZeeRooo](https://forum.xda-developers.com/android/apps-games/app-materialfbook-minimalist-facebook-t3477896) is a great one. Thank you so much to everyone who contributed to this project, it was an amazing learning experience for me and it helped kick my career off! 6 | 7 | 8 | Get it on F-Droid 9 | 10 | Get it on Google Play 11 | 12 | ~~Please submit any bugs or feature requests on the [issue tracker](https://github.com/JakeLane/Toffeed/issues/new).~~ 13 | 14 | ~~Any contributions to localisation are greatly appreciated. You can read how to contribute [here](https://github.com/JakeLane/Toffeed/wiki/Localisation)~~ 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | lintOptions { 8 | checkReleaseBuilds false 9 | } 10 | 11 | defaultConfig { 12 | applicationId "me.jakelane.wrapperforfacebook" 13 | minSdkVersion 17 14 | targetSdkVersion 23 15 | versionCode 14 16 | versionName "1.5.5" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled true 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | testCompile 'junit:junit:4.12' 28 | compile 'com.android.support:appcompat-v7:23.4.0' 29 | compile 'com.android.support:design:23.4.0' 30 | compile 'com.facebook.android:facebook-android-sdk:4.6.0' 31 | compile('com.mikepenz:actionitembadge:3.1.9@aar') { 32 | transitive = true 33 | } 34 | compile 'com.squareup.picasso:picasso:2.5.2' 35 | compile 'de.hdodenhof:circleimageview:2.0.0' 36 | compile 'com.github.JakeLane:Android-AdvancedWebView:-SNAPSHOT' 37 | compile 'com.github.clans:fab:1.6.2' 38 | compile 'com.android.support:customtabs:23.4.0' 39 | compile 'com.github.ahorn:android-rss:master' 40 | compile 'org.jsoup:jsoup:1.8.3' 41 | compile 'com.greysonparrelli.permiso:permiso:0.1.3' 42 | } 43 | -------------------------------------------------------------------------------- /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 /home/jake/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Picasso 20 | -dontwarn com.squareup.okhttp.** 21 | 22 | # AdvancedWebView 23 | -keep class * extends android.webkit.WebChromeClient { *; } 24 | -dontwarn im.delight.android.webview.** 25 | 26 | # Local fun 27 | -keepclassmembers class * { 28 | @android.webkit.JavascriptInterface ; 29 | } 30 | 31 | -keeppackagenames org.jsoup.nodes 32 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/jakelane/wrapperforfacebook/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 52 | 55 | 58 | 61 | 64 | 67 | 70 | 71 | 72 | 73 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLane/Toffeed/700f63e6f04265ae6d09fcf6c5174d5f86410429/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/CustomWebChromeClient.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.webkit.WebChromeClient; 7 | import android.webkit.WebView; 8 | import android.widget.FrameLayout; 9 | 10 | import com.github.clans.fab.FloatingActionMenu; 11 | 12 | 13 | class CustomWebChromeClient extends WebChromeClient { 14 | private final MainActivity mActivity; 15 | private final WebView mWebView; 16 | private final ViewGroup mCustomViewContainer; 17 | private final FloatingActionMenu mMenuFAB; 18 | private View mVideoProgressView; 19 | private View mCustomView; 20 | private CustomViewCallback customViewCallback; 21 | 22 | public CustomWebChromeClient(MainActivity activity, WebView webview, FrameLayout viewcontainer) { 23 | mActivity = activity; 24 | mWebView = webview; 25 | mCustomViewContainer = viewcontainer; 26 | mMenuFAB = (FloatingActionMenu) activity.findViewById(R.id.menuFAB); 27 | } 28 | 29 | @Override 30 | public void onShowCustomView(View view, CustomViewCallback callback) { 31 | // if a view already exists then immediately terminate the new one 32 | if (mCustomView != null) { 33 | callback.onCustomViewHidden(); 34 | return; 35 | } 36 | 37 | mCustomView = view; 38 | mWebView.setVisibility(View.GONE); 39 | mMenuFAB.hideMenuButton(true); 40 | mCustomViewContainer.setVisibility(View.VISIBLE); 41 | mCustomViewContainer.addView(view); 42 | customViewCallback = callback; 43 | } 44 | 45 | @Override 46 | public View getVideoLoadingProgressView() { 47 | if (mVideoProgressView == null) { 48 | LayoutInflater inflater = LayoutInflater.from(mActivity); 49 | mVideoProgressView = inflater.inflate(R.layout.video_progress, mWebView, false); 50 | } 51 | return mVideoProgressView; 52 | } 53 | 54 | @Override 55 | public void onHideCustomView() { 56 | super.onHideCustomView(); //To change body of overridden methods use File | Settings | File Templates. 57 | if (mCustomView == null) { 58 | return; 59 | } 60 | 61 | mWebView.setVisibility(View.VISIBLE); 62 | mMenuFAB.showMenuButton(true); 63 | mCustomViewContainer.setVisibility(View.GONE); 64 | 65 | // Hide the custom view. 66 | mCustomView.setVisibility(View.GONE); 67 | 68 | // Remove the custom view from its container. 69 | mCustomViewContainer.removeView(mCustomView); 70 | customViewCallback.onCustomViewHidden(); 71 | 72 | mCustomView = null; 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/Helpers.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.app.Activity; 4 | import android.support.design.widget.Snackbar; 5 | import android.view.Menu; 6 | import android.view.View; 7 | import android.webkit.CookieManager; 8 | 9 | import com.facebook.login.LoginManager; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | class Helpers { 15 | public static final String LogTag = "FBWrapper"; 16 | static final List FB_PERMISSIONS = Arrays.asList("public_profile", "user_friends"); 17 | 18 | // Method to retrieve a single cookie 19 | public static String getCookie() { 20 | CookieManager cookieManager = CookieManager.getInstance(); 21 | String cookies = cookieManager.getCookie(MainActivity.FACEBOOK_URL_BASE); 22 | if (cookies != null) { 23 | String[] temp = cookies.split(";"); 24 | for (String ar1 : temp) { 25 | if (ar1.contains("c_user")) { 26 | String[] temp1 = ar1.split("="); 27 | return temp1[1]; 28 | } 29 | } 30 | } 31 | // Return null as we found no cookie 32 | return null; 33 | } 34 | 35 | // Prompt a login 36 | public static Snackbar loginPrompt(final View view) { 37 | final Snackbar snackBar = Snackbar.make(view, R.string.not_logged_in, Snackbar.LENGTH_INDEFINITE); 38 | snackBar.setAction(R.string.login_button, new View.OnClickListener() { 39 | @Override 40 | public void onClick(View v) { 41 | LoginManager.getInstance().logInWithReadPermissions((Activity) view.getContext(), FB_PERMISSIONS); 42 | } 43 | }); 44 | snackBar.show(); 45 | return snackBar; 46 | } 47 | 48 | // Uncheck all items menu 49 | public static void uncheckRadioMenu(Menu menu) { 50 | for (int i = 0; i < menu.size(); i++) { 51 | if (menu.getItem(i).isChecked()) { 52 | menu.getItem(i).setChecked(false); 53 | return; 54 | } 55 | } 56 | } 57 | 58 | public static boolean isInteger(String str) { 59 | return (str.matches("^-?\\d+$")); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/JavaScriptHelpers.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.net.UrlQuerySanitizer; 4 | import android.webkit.WebView; 5 | 6 | class JavaScriptHelpers { 7 | private static final int BADGE_UPDATE_INTERVAL = 15000; 8 | 9 | public static void updateCurrentTab(WebView view) { 10 | // Get the currently open tab and check on the navigation menu 11 | view.loadUrl("javascript:(function()%7Btry%7Bvar%20jewel%3Ddocument.querySelector(%22.popoverOpen%22).id%3B%22feed_jewel%22%3D%3Djewel%3Fdocument.querySelector('a%5Bhref*%3D%22%2Fhome.php%3Fsk%3Dh_nor%22%5D')%3Fandroid.getCurrent(%22most_recent%22)%3Aandroid.getCurrent(%22top_stories%22)%3Aandroid.getCurrent(jewel)%7Dcatch(_)%7Bandroid.getCurrent(%22null%22)%7D%7D)()"); 12 | } 13 | 14 | public static void updateNums(WebView view) { 15 | view.loadUrl("javascript:(function()%7Bandroid.getNums(document.querySelector(%22%23notifications_jewel%20%3E%20a%20%3E%20div%20%3E%20span%5Bdata-sigil%3Dcount%5D%22).innerHTML%2Cdocument.querySelector(%22%23messages_jewel%20%3E%20a%20%3E%20div%20%3E%20span%5Bdata-sigil%3Dcount%5D%22).innerHTML%2Cdocument.querySelector(%22%23requests_jewel%20%3E%20a%20%3E%20div%20%3E%20span%5Bdata-sigil%3Dcount%5D%22).innerHTML)%7D)()"); 16 | } 17 | 18 | public static void updateNumsService(WebView view) { 19 | view.loadUrl("javascript:(function()%7Bfunction%20n_s()%7Bandroid.getNums(document.querySelector(%22%23notifications_jewel%20%3E%20a%20%3E%20div%20%3E%20span%5Bdata-sigil%3Dcount%5D%22).innerHTML%2Cdocument.querySelector(%22%23messages_jewel%20%3E%20a%20%3E%20div%20%3E%20span%5Bdata-sigil%3Dcount%5D%22).innerHTML%2Cdocument.querySelector(%22%23requests_jewel%20%3E%20a%20%3E%20div%20%3E%20span%5Bdata-sigil%3Dcount%5D%22).innerHTML)%2CsetTimeout(n_s%2C" + BADGE_UPDATE_INTERVAL + ")%7Dtry%7Bn_s()%7Dcatch(_)%7B%7D%7D)()"); 20 | } 21 | 22 | public static void paramLoader(WebView view, String url) { 23 | UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(); 24 | sanitizer.setAllowUnregisteredParamaters(true); 25 | sanitizer.parseUrl(url); 26 | String param = sanitizer.getValue("pageload"); 27 | if (param != null) { 28 | switch (param) { 29 | case "composer": 30 | view.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('button%5Bname%3D%22view_overview%22%5D').click()%7Dcatch(_)%7B%7D%7D)()"); 31 | break; 32 | case "composer_photo": 33 | view.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('button%5Bname%3D%22view_photo%22%5D').click()%7Dcatch(_)%7B%7D%7D)()"); 34 | break; 35 | case "composer_checkin": 36 | view.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('button%5Bname%3D%22view_location%22%5D').click()%7Dcatch(_)%7B%7D%7D)()"); 37 | break; 38 | default: 39 | break; 40 | } 41 | } 42 | 43 | } 44 | 45 | public static void loadCSS(WebView view, String css) { 46 | // Inject CSS string to the HEAD of the webpage 47 | view.loadUrl("javascript:(function()%7Bvar%20styles%3Ddocument.createElement('style')%3Bstyles.innerHTML%3D'" + css + "'%2Cstyles.onload%3Dandroid.loadingCompleted()%2Cdocument.getElementsByTagName('head')%5B0%5D.appendChild(styles)%7D)()"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/JavaScriptInterfaces.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.content.SharedPreferences; 4 | import android.preference.PreferenceManager; 5 | import android.webkit.JavascriptInterface; 6 | 7 | @SuppressWarnings("unused") 8 | class JavaScriptInterfaces { 9 | private final MainActivity mContext; 10 | private final SharedPreferences mPreferences; 11 | 12 | // Instantiate the interface and set the context 13 | JavaScriptInterfaces(MainActivity c) { 14 | mContext = c; 15 | mPreferences = PreferenceManager.getDefaultSharedPreferences(c); 16 | } 17 | 18 | @JavascriptInterface 19 | public void loadingCompleted() { 20 | mContext.runOnUiThread(new Runnable() { 21 | @Override 22 | public void run() { 23 | mContext.setLoading(false); 24 | } 25 | }); 26 | } 27 | 28 | @JavascriptInterface 29 | public void getCurrent(final String value) { 30 | mContext.runOnUiThread(new Runnable() { 31 | @Override 32 | public void run() { 33 | switch (value) { 34 | case "top_stories": 35 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_MOST_RECENT_MENU, true)) { 36 | mContext.mNavigationView.setCheckedItem(R.id.nav_top_stories); 37 | } else { 38 | mContext.mNavigationView.setCheckedItem(R.id.nav_news); 39 | } 40 | break; 41 | case "most_recent": 42 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_MOST_RECENT_MENU, true)) { 43 | mContext.mNavigationView.setCheckedItem(R.id.nav_most_recent); 44 | } else { 45 | mContext.mNavigationView.setCheckedItem(R.id.nav_news); 46 | } 47 | break; 48 | case "requests_jewel": 49 | mContext.mNavigationView.setCheckedItem(R.id.nav_friendreq); 50 | break; 51 | case "messages_jewel": 52 | mContext.mNavigationView.setCheckedItem(R.id.nav_messages); 53 | break; 54 | case "notifications_jewel": 55 | Helpers.uncheckRadioMenu(mContext.mNavigationView.getMenu()); 56 | break; 57 | case "search_jewel": 58 | mContext.mNavigationView.setCheckedItem(R.id.nav_search); 59 | break; 60 | case "bookmarks_jewel": 61 | mContext.mNavigationView.setCheckedItem(R.id.nav_mainmenu); 62 | break; 63 | default: 64 | Helpers.uncheckRadioMenu(mContext.mNavigationView.getMenu()); 65 | break; 66 | } 67 | } 68 | }); 69 | } 70 | 71 | @JavascriptInterface 72 | public void getNums(final String notifications, final String messages, final String requests) { 73 | final int notifications_int = Helpers.isInteger(notifications) ? Integer.parseInt(notifications) : 0; 74 | final int messages_int = Helpers.isInteger(messages) ? Integer.parseInt(messages) : 0; 75 | final int requests_int = Helpers.isInteger(requests) ? Integer.parseInt(requests): 0; 76 | mContext.runOnUiThread(new Runnable() { 77 | @Override 78 | public void run() { 79 | mContext.setNotificationNum(notifications_int); 80 | mContext.setMessagesNum(messages_int); 81 | mContext.setRequestsNum(requests_int); 82 | } 83 | }); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Color; 8 | import android.graphics.drawable.BitmapDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.os.Bundle; 11 | import android.os.Handler; 12 | import android.preference.PreferenceManager; 13 | import android.support.annotation.NonNull; 14 | import android.support.design.widget.NavigationView; 15 | import android.support.design.widget.Snackbar; 16 | import android.support.v4.content.res.ResourcesCompat; 17 | import android.support.v4.view.GravityCompat; 18 | import android.support.v4.widget.DrawerLayout; 19 | import android.support.v4.widget.SwipeRefreshLayout; 20 | import android.support.v7.app.ActionBarDrawerToggle; 21 | import android.support.v7.app.AppCompatActivity; 22 | import android.support.v7.widget.Toolbar; 23 | import android.util.Log; 24 | import android.view.Menu; 25 | import android.view.MenuItem; 26 | import android.view.View; 27 | import android.webkit.URLUtil; 28 | import android.widget.FrameLayout; 29 | import android.widget.ImageView; 30 | import android.widget.TextView; 31 | 32 | import com.facebook.AccessToken; 33 | import com.facebook.CallbackManager; 34 | import com.facebook.FacebookCallback; 35 | import com.facebook.FacebookException; 36 | import com.facebook.FacebookSdk; 37 | import com.facebook.GraphRequest; 38 | import com.facebook.GraphResponse; 39 | import com.facebook.login.LoginBehavior; 40 | import com.facebook.login.LoginManager; 41 | import com.facebook.login.LoginResult; 42 | import com.github.clans.fab.FloatingActionMenu; 43 | import com.greysonparrelli.permiso.Permiso; 44 | import com.mikepenz.actionitembadge.library.ActionItemBadge; 45 | import com.mikepenz.actionitembadge.library.utils.BadgeStyle; 46 | import com.squareup.picasso.Picasso; 47 | import com.squareup.picasso.Target; 48 | 49 | import org.json.JSONException; 50 | import org.json.JSONObject; 51 | 52 | import java.io.UnsupportedEncodingException; 53 | import java.net.URLEncoder; 54 | import java.util.Arrays; 55 | import java.util.List; 56 | 57 | import im.delight.android.webview.AdvancedWebView; 58 | 59 | public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { 60 | static final String FACEBOOK_URL_BASE = "https://m.facebook.com/"; 61 | private static final String FACEBOOK_URL_BASE_ENCODED = "https%3A%2F%2Fm.facebook.com%2F"; 62 | private static final List HOSTNAMES = Arrays.asList("facebook.com", "*.facebook.com", "*.fbcdn.net", "*.akamaihd.net"); 63 | private final BadgeStyle BADGE_SIDE_FULL = new BadgeStyle(BadgeStyle.Style.LARGE, R.layout.menu_badge_full, R.color.colorAccent, R.color.colorAccent, Color.WHITE); 64 | 65 | // Members 66 | SwipeRefreshLayout swipeView; 67 | NavigationView mNavigationView; 68 | View mCoordinatorLayoutView; 69 | private FloatingActionMenu mMenuFAB; 70 | private AdvancedWebView mWebView; 71 | private final View.OnClickListener mFABClickListener = new View.OnClickListener() { 72 | @Override 73 | public void onClick(View v) { 74 | switch (v.getId()) { 75 | case R.id.textFAB: 76 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('button%5Bname%3D%22view_overview%22%5D').click()%7Dcatch(_)%7Bwindow.location.href%3D%22" + FACEBOOK_URL_BASE_ENCODED + "%3Fpageload%3Dcomposer%22%7D%7D)()"); 77 | break; 78 | case R.id.photoFAB: 79 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('button%5Bname%3D%22view_photo%22%5D').click()%7Dcatch(_)%7Bwindow.location.href%3D%22" + FACEBOOK_URL_BASE_ENCODED + "%3Fpageload%3Dcomposer_photo%22%7D%7D)()"); 80 | break; 81 | case R.id.checkinFAB: 82 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('button%5Bname%3D%22view_location%22%5D').click()%7Dcatch(_)%7Bwindow.location.href%3D%22" + FACEBOOK_URL_BASE_ENCODED + "%3Fpageload%3Dcomposer_checkin%22%7D%7D)()"); 83 | break; 84 | default: 85 | break; 86 | } 87 | mMenuFAB.close(true); 88 | } 89 | }; 90 | private MenuItem mNotificationButton; 91 | private CallbackManager callbackManager; 92 | private Snackbar loginSnackbar = null; 93 | @SuppressWarnings("FieldCanBeLocal") // Will be garbage collected as a local variable 94 | private SharedPreferences.OnSharedPreferenceChangeListener listener; 95 | private boolean requiresReload = false; 96 | private String mUserLink = null; 97 | private SharedPreferences mPreferences; 98 | 99 | @Override 100 | protected void onCreate(Bundle savedInstanceState) { 101 | super.onCreate(savedInstanceState); 102 | FacebookSdk.sdkInitialize(this.getApplicationContext()); 103 | setContentView(R.layout.activity_main); 104 | Permiso.getInstance().setActivity(this); 105 | 106 | // Preferences 107 | PreferenceManager.setDefaultValues(this, R.xml.settings, false); 108 | mPreferences = PreferenceManager.getDefaultSharedPreferences(this); 109 | listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 110 | public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 111 | switch (key) { 112 | case SettingsActivity.KEY_PREF_JUMP_TOP_BUTTON: 113 | mNavigationView.getMenu().findItem(R.id.nav_jump_top).setVisible(prefs.getBoolean(key, false)); 114 | break; 115 | case SettingsActivity.KEY_PREF_STOP_IMAGES: 116 | mWebView.getSettings().setBlockNetworkImage(prefs.getBoolean(key, false)); 117 | requiresReload = true; 118 | break; 119 | case SettingsActivity.KEY_PREF_BACK_BUTTON: 120 | mNavigationView.getMenu().findItem(R.id.nav_back).setVisible(prefs.getBoolean(key, false)); 121 | break; 122 | case SettingsActivity.KEY_PREF_MESSAGING: 123 | mNavigationView.getMenu().findItem(R.id.nav_messages).setVisible(prefs.getBoolean(key, false)); 124 | break; 125 | case SettingsActivity.KEY_PREF_LOCATION: 126 | if (prefs.getBoolean(key, false)) { 127 | Permiso.getInstance().requestPermissions(new Permiso.IOnPermissionResult() { 128 | @Override 129 | public void onPermissionResult(Permiso.ResultSet resultSet) { 130 | if (resultSet.areAllPermissionsGranted()) { 131 | mWebView.setGeolocationEnabled(true); 132 | } else { 133 | Snackbar.make(mCoordinatorLayoutView, R.string.permission_denied, Snackbar.LENGTH_SHORT).show(); 134 | } 135 | } 136 | 137 | @Override 138 | public void onRationaleRequested(Permiso.IOnRationaleProvided callback, String... permissions) { 139 | // TODO Permiso.getInstance().showRationaleInDialog("Title", "Message", null, callback); 140 | callback.onRationaleProvided(); 141 | } 142 | }, Manifest.permission.ACCESS_FINE_LOCATION); 143 | } 144 | break; 145 | case SettingsActivity.KEY_PREF_MOST_RECENT_MENU: 146 | boolean most_recent = prefs.getBoolean(key, true); 147 | mNavigationView.getMenu().findItem(R.id.nav_news).setVisible(!most_recent); 148 | mNavigationView.getMenu().findItem(R.id.nav_top_stories).setVisible(most_recent); 149 | mNavigationView.getMenu().findItem(R.id.nav_most_recent).setVisible(most_recent); 150 | requiresReload = true; 151 | break; 152 | case SettingsActivity.KEY_PREF_FAB_SCROLL: 153 | mMenuFAB.showMenuButton(true); 154 | break; 155 | case SettingsActivity.KEY_PREF_HIDE_EDITOR: 156 | requiresReload = true; 157 | break; 158 | case SettingsActivity.KEY_PREF_HIDE_SPONSORED: 159 | requiresReload = true; 160 | break; 161 | case SettingsActivity.KEY_PREF_HIDE_BIRTHDAYS: 162 | requiresReload = true; 163 | break; 164 | case SettingsActivity.KEY_PREF_NOTIFICATIONS_ENABLED: 165 | PollReceiver.scheduleAlarms(getApplicationContext(), false); 166 | break; 167 | case SettingsActivity.KEY_PREF_NOTIFICATION_INTERVAL: 168 | PollReceiver.scheduleAlarms(getApplicationContext(), false); 169 | break; 170 | default: 171 | break; 172 | } 173 | } 174 | }; 175 | mPreferences.registerOnSharedPreferenceChangeListener(listener); 176 | 177 | // Setup the toolbar 178 | Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar); 179 | setSupportActionBar(mToolbar); 180 | 181 | // Setup the DrawLayout 182 | final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 183 | final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); 184 | drawer.addDrawerListener(toggle); 185 | toggle.syncState(); 186 | 187 | mNavigationView = (NavigationView) findViewById(R.id.nav_view); 188 | mNavigationView.setNavigationItemSelectedListener(this); 189 | 190 | // Create the badge for messages 191 | ActionItemBadge.update(this, mNavigationView.getMenu().findItem(R.id.nav_messages), (Drawable) null, BADGE_SIDE_FULL, Integer.MIN_VALUE); 192 | ActionItemBadge.update(this, mNavigationView.getMenu().findItem(R.id.nav_friendreq), (Drawable) null, BADGE_SIDE_FULL, Integer.MIN_VALUE); 193 | 194 | // Hide buttons if they are disabled 195 | if (!mPreferences.getBoolean(SettingsActivity.KEY_PREF_MESSAGING, false)) { 196 | mNavigationView.getMenu().findItem(R.id.nav_messages).setVisible(false); 197 | } 198 | if (!mPreferences.getBoolean(SettingsActivity.KEY_PREF_JUMP_TOP_BUTTON, false)) { 199 | mNavigationView.getMenu().findItem(R.id.nav_jump_top).setVisible(false); 200 | } 201 | if (!mPreferences.getBoolean(SettingsActivity.KEY_PREF_BACK_BUTTON, false)) { 202 | mNavigationView.getMenu().findItem(R.id.nav_back).setVisible(false); 203 | } 204 | boolean most_recent = mPreferences.getBoolean(SettingsActivity.KEY_PREF_MOST_RECENT_MENU, true); 205 | mNavigationView.getMenu().findItem(R.id.nav_news).setVisible(!most_recent); 206 | mNavigationView.getMenu().findItem(R.id.nav_top_stories).setVisible(most_recent); 207 | mNavigationView.getMenu().findItem(R.id.nav_most_recent).setVisible(most_recent); 208 | 209 | // Bind the Coordinator to member 210 | mCoordinatorLayoutView = findViewById(R.id.coordinatorLayout); 211 | 212 | // Start the Swipe to reload listener 213 | swipeView = (SwipeRefreshLayout) findViewById(R.id.swipeLayout); 214 | swipeView.setColorSchemeResources(R.color.colorPrimary); 215 | swipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 216 | @Override 217 | public void onRefresh() { 218 | mWebView.reload(); 219 | } 220 | }); 221 | 222 | // Inflate the FAB menu 223 | mMenuFAB = (FloatingActionMenu) findViewById(R.id.menuFAB); 224 | // Nasty hack to get the FAB menu button 225 | mMenuFAB.getChildAt(3).setOnLongClickListener(new View.OnLongClickListener() { 226 | @Override 227 | public boolean onLongClick(View v) { 228 | mMenuFAB.hideMenu(true); 229 | Handler handler = new Handler(); 230 | handler.postDelayed(new Runnable() { 231 | @Override 232 | public void run() { 233 | // Show your View after 3 seconds 234 | mMenuFAB.showMenu(true); 235 | } 236 | }, 3000); 237 | return false; 238 | } 239 | }); 240 | findViewById(R.id.textFAB).setOnClickListener(mFABClickListener); 241 | findViewById(R.id.photoFAB).setOnClickListener(mFABClickListener); 242 | findViewById(R.id.checkinFAB).setOnClickListener(mFABClickListener); 243 | 244 | // Load the WebView 245 | mWebView = (AdvancedWebView) findViewById(R.id.webview); 246 | assert mWebView != null; 247 | mWebView.addPermittedHostnames(HOSTNAMES); 248 | mWebView.setGeolocationEnabled(mPreferences.getBoolean(SettingsActivity.KEY_PREF_LOCATION, false)); 249 | 250 | mWebView.setListener(this, new WebViewListener(this, mWebView)); 251 | mWebView.addJavascriptInterface(new JavaScriptInterfaces(this), "android"); 252 | registerForContextMenu(mWebView); 253 | 254 | mWebView.getSettings().setBlockNetworkImage(mPreferences.getBoolean(SettingsActivity.KEY_PREF_STOP_IMAGES, false)); 255 | mWebView.getSettings().setAppCacheEnabled(true); 256 | mWebView.getSettings().setSupportZoom(true); 257 | mWebView.getSettings().setBuiltInZoomControls(false); 258 | mWebView.getSettings().setLoadWithOverviewMode(true); 259 | mWebView.getSettings().setUseWideViewPort(true); 260 | // Impersonate iPhone to prevent advertising garbage 261 | mWebView.getSettings().setUserAgentString("Mozilla/5.0 (Linux; Android 2.2; Nexus 5 Build/_BuildID_) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36"); 262 | 263 | // Long press 264 | registerForContextMenu(mWebView); 265 | mWebView.setLongClickable(true); 266 | mWebView.setWebChromeClient(new CustomWebChromeClient(this, mWebView, (FrameLayout) findViewById(R.id.fullscreen_custom_content))); 267 | 268 | // Add OnClick listener to Profile picture 269 | ImageView profileImage = (ImageView) mNavigationView.getHeaderView(0).findViewById(R.id.profile_picture); 270 | profileImage.setClickable(true); 271 | profileImage.setOnClickListener(new View.OnClickListener() { 272 | @Override 273 | public void onClick(View v) { 274 | if (mUserLink != null) { 275 | drawer.closeDrawers(); 276 | mWebView.loadUrl(mUserLink); 277 | } 278 | } 279 | }); 280 | 281 | callbackManager = CallbackManager.Factory.create(); 282 | 283 | FacebookCallback loginResult = new FacebookCallback() { 284 | @Override 285 | public void onSuccess(LoginResult loginResult) { 286 | mWebView.loadUrl(chooseUrl()); 287 | updateUserInfo(); 288 | } 289 | 290 | @Override 291 | public void onCancel() { 292 | checkLoggedInState(); 293 | } 294 | 295 | @Override 296 | public void onError(FacebookException error) { 297 | Snackbar.make(mCoordinatorLayoutView, R.string.error_login, Snackbar.LENGTH_LONG).show(); 298 | Log.e(Helpers.LogTag, error.toString()); 299 | LoginManager.getInstance().logOut(); 300 | checkLoggedInState(); 301 | } 302 | }; 303 | 304 | LoginManager.getInstance().setLoginBehavior(LoginBehavior.WEB_ONLY); 305 | LoginManager.getInstance().registerCallback(callbackManager, loginResult); 306 | 307 | if (checkLoggedInState()) { 308 | mWebView.loadUrl(chooseUrl()); 309 | updateUserInfo(); 310 | } 311 | } 312 | 313 | @Override 314 | protected void onResume() { 315 | super.onResume(); 316 | mWebView.onResume(); 317 | Permiso.getInstance().setActivity(this); 318 | 319 | // Check if we need to show a page reload snackbar 320 | if (requiresReload) { 321 | Snackbar reloadSnackbar = Snackbar.make(mCoordinatorLayoutView, R.string.hide_editor_newsfeed_snackbar, Snackbar.LENGTH_LONG); 322 | reloadSnackbar.setAction(R.string.menu_refresh, new View.OnClickListener() { 323 | @Override 324 | public void onClick(View v) { 325 | mWebView.reload(); 326 | } 327 | }); 328 | reloadSnackbar.show(); 329 | requiresReload = false; 330 | } 331 | registerForContextMenu(mWebView); 332 | } 333 | 334 | @Override 335 | protected void onDestroy() { 336 | mWebView.onDestroy(); 337 | super.onDestroy(); 338 | } 339 | 340 | @Override 341 | protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { 342 | super.onActivityResult(requestCode, resultCode, data); 343 | mWebView.onActivityResult(requestCode, resultCode, data); 344 | callbackManager.onActivityResult(requestCode, resultCode, data); 345 | } 346 | 347 | @Override 348 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 349 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 350 | Permiso.getInstance().onRequestPermissionResult(requestCode, permissions, grantResults); 351 | } 352 | 353 | @Override 354 | public void onBackPressed() { 355 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 356 | if (drawer.isDrawerOpen(GravityCompat.START)) { 357 | drawer.closeDrawer(GravityCompat.START); 358 | } else if (mWebView.canGoBack()) { 359 | mWebView.goBack(); 360 | } else { 361 | super.onBackPressed(); 362 | } 363 | } 364 | 365 | @Override 366 | public boolean onCreateOptionsMenu(Menu menu) { 367 | // Inflate the menu; this adds items to the action bar if it is present. 368 | getMenuInflater().inflate(R.menu.main, menu); 369 | mNotificationButton = menu.findItem(R.id.action_notifications); 370 | 371 | ActionItemBadge.update(this, mNotificationButton, ResourcesCompat.getDrawable(getResources(), R.drawable.ic_menu_notifications, null), ActionItemBadge.BadgeStyles.RED, Integer.MIN_VALUE); 372 | return true; 373 | } 374 | 375 | @Override 376 | public boolean onOptionsItemSelected(MenuItem item) { 377 | // Handle action bar item clicks here 378 | int id = item.getItemId(); 379 | if (id == R.id.action_notifications) { 380 | // Load the notification page 381 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('%23notifications_jewel%20%3E%20a').click()%7Dcatch(_)%7Bwindow.location.href%3D'" + FACEBOOK_URL_BASE_ENCODED + "notifications.php'%7D%7D)()"); 382 | Helpers.uncheckRadioMenu(mNavigationView.getMenu()); 383 | } 384 | 385 | // Update the notifications 386 | JavaScriptHelpers.updateNums(mWebView); 387 | return super.onOptionsItemSelected(item); 388 | } 389 | 390 | @Override 391 | public boolean onNavigationItemSelected(MenuItem item) { 392 | // Handle navigation view item clicks here. 393 | switch (item.getItemId()) { 394 | case R.id.nav_news: 395 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('%23feed_jewel%20%3E%20a').click()%7Dcatch(_)%7Bwindow.location.href%3D'" + FACEBOOK_URL_BASE_ENCODED + "home.php'%7D%7D)()"); 396 | item.setChecked(true); 397 | case R.id.nav_top_stories: 398 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('a%5Bhref*%3D%22%2Fhome.php%3Fsk%3Dh_nor%22%5D').click()%7Dcatch(_)%7Bwindow.location.href%3D%22" + FACEBOOK_URL_BASE_ENCODED + "home.php%3Fsk%3Dh_nor%22%7D%7D)()"); 399 | item.setChecked(true); 400 | break; 401 | case R.id.nav_most_recent: 402 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('a%5Bhref*%3D%22%2Fhome.php%3Fsk%3Dh_chr%22%5D').click()%7Dcatch(_)%7Bwindow.location.href%3D%22" + FACEBOOK_URL_BASE_ENCODED + "home.php%3Fsk%3Dh_chr%22%7D%7D)()"); 403 | item.setChecked(true); 404 | break; 405 | case R.id.nav_friendreq: 406 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('%23requests_jewel%20%3E%20a').click()%7Dcatch(_)%7Bwindow.location.href%3D'" + FACEBOOK_URL_BASE_ENCODED + "friends%2Fcenter%2Frequests%2F'%7D%7D)()"); 407 | item.setChecked(true); 408 | break; 409 | case R.id.nav_messages: 410 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('%23messages_jewel%20%3E%20a').click()%7Dcatch(_)%7Bwindow.location.href%3D'" + FACEBOOK_URL_BASE_ENCODED + "messages%2F'%7D%7D)()"); 411 | JavaScriptHelpers.updateNums(mWebView); 412 | item.setChecked(true); 413 | break; 414 | case R.id.nav_search: 415 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('%23search_jewel%20%3E%20a').click()%7Dcatch(_)%7Bwindow.location.href%3D'" + FACEBOOK_URL_BASE_ENCODED + "search%2F'%7D%7D)()"); 416 | item.setChecked(true); 417 | break; 418 | case R.id.nav_mainmenu: 419 | mWebView.loadUrl("javascript:(function()%7Btry%7Bdocument.querySelector('%23bookmarks_jewel%20%3E%20a').click()%7Dcatch(_)%7Bwindow.location.href%3D'" + FACEBOOK_URL_BASE_ENCODED + "home.php'%7D%7D)()"); 420 | item.setChecked(true); 421 | break; 422 | case R.id.nav_fblogin: 423 | LoginManager.getInstance().logInWithReadPermissions(this, Helpers.FB_PERMISSIONS); 424 | break; 425 | case R.id.nav_jump_top: 426 | mWebView.scrollTo(0, 0); 427 | break; 428 | case R.id.nav_back: 429 | mWebView.goBack(); 430 | break; 431 | case R.id.nav_reload: 432 | mWebView.reload(); 433 | break; 434 | case R.id.nav_forward: 435 | mWebView.goForward(); 436 | break; 437 | case R.id.nav_settings: 438 | Intent settingsActivity = new Intent(MainActivity.this, SettingsActivity.class); 439 | startActivity(settingsActivity); 440 | break; 441 | default: 442 | break; 443 | } 444 | 445 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); 446 | drawer.closeDrawer(GravityCompat.START); 447 | return true; 448 | } 449 | 450 | public void setLoading(boolean loading) { 451 | // Toggle the WebView and Spinner visibility 452 | mWebView.setVisibility(loading ? View.GONE : View.VISIBLE); 453 | swipeView.setRefreshing(loading); 454 | } 455 | 456 | public boolean checkLoggedInState() { 457 | if (loginSnackbar != null) { 458 | loginSnackbar.dismiss(); 459 | } 460 | 461 | if (AccessToken.getCurrentAccessToken() != null && Helpers.getCookie() != null) { 462 | // Logged in, show webview 463 | mWebView.setVisibility(View.VISIBLE); 464 | 465 | // Hide login button 466 | mNavigationView.getMenu().findItem(R.id.nav_fblogin).setVisible(false); 467 | 468 | // Enable navigation buttons 469 | mNavigationView.getMenu().setGroupEnabled(R.id.group_fbnav, true); 470 | 471 | // Start the Notification service (if not already running) 472 | PollReceiver.scheduleAlarms(getApplicationContext(), false); 473 | return true; 474 | } else { 475 | // Not logged in (possibly logged into Facebook OAuth and/or webapp) 476 | loginSnackbar = Helpers.loginPrompt(mCoordinatorLayoutView); 477 | setLoading(false); 478 | mWebView.setVisibility(View.GONE); 479 | 480 | // Show login button 481 | mNavigationView.getMenu().findItem(R.id.nav_fblogin).setVisible(true); 482 | 483 | // Disable navigation buttons 484 | mNavigationView.getMenu().setGroupEnabled(R.id.group_fbnav, false); 485 | 486 | // Cancel the Notification service if we are logged out 487 | PollReceiver.scheduleAlarms(getApplicationContext(), true); 488 | 489 | // Kill the Feed URL, so we don't get the wrong notifications 490 | mPreferences.edit().putString("feed_uri", null).apply(); 491 | return false; 492 | } 493 | } 494 | 495 | private void updateUserInfo() { 496 | GraphRequest request = GraphRequest.newMeRequest(AccessToken.getCurrentAccessToken(), new GraphRequest.GraphJSONObjectCallback() { 497 | @Override 498 | public void onCompleted(JSONObject object, GraphResponse response) { 499 | // Update header 500 | try { 501 | String userID = object.getString("id"); 502 | mUserLink = object.getString("link"); 503 | 504 | // Set the user's name under the header 505 | ((TextView) findViewById(R.id.profile_name)).setText(object.getString("name")); 506 | 507 | // Set the cover photo with resizing 508 | Picasso.with(getApplicationContext()).load("https://graph.facebook.com/" + userID + "/picture?type=large").into((ImageView) findViewById(R.id.profile_picture)); 509 | 510 | final View header = findViewById(R.id.header_layout); 511 | Picasso.with(getApplicationContext()).load(object.getJSONObject("cover").getString("source")).resize(header.getWidth(), header.getHeight()).centerCrop().error(R.drawable.side_nav_bar).into(new Target() { 512 | @Override 513 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { 514 | Log.v(Helpers.LogTag, "Set cover photo"); 515 | header.setBackground(new BitmapDrawable(getResources(), bitmap)); 516 | } 517 | 518 | @Override 519 | public void onBitmapFailed(Drawable errorDrawable) {} 520 | 521 | @Override 522 | public void onPrepareLoad(Drawable placeHolderDrawable) {} 523 | }); 524 | } catch (NullPointerException e) { 525 | Snackbar.make(mCoordinatorLayoutView, R.string.error_facebook_noconnection, Snackbar.LENGTH_LONG).show(); 526 | } catch (JSONException e) { 527 | e.printStackTrace(); 528 | Snackbar.make(mCoordinatorLayoutView, R.string.error_facebook_error, Snackbar.LENGTH_LONG).show(); 529 | } catch (Exception e) { 530 | e.printStackTrace(); 531 | Snackbar.make(mCoordinatorLayoutView, R.string.error_super_wrong, Snackbar.LENGTH_LONG).show(); 532 | } 533 | } 534 | }); 535 | 536 | Bundle parameters = new Bundle(); 537 | parameters.putString("fields", "id,name,cover,link"); 538 | request.setParameters(parameters); 539 | request.executeAsync(); 540 | } 541 | 542 | public void setNotificationNum(int num) { 543 | if (num > 0) { 544 | ActionItemBadge.update(mNotificationButton, ResourcesCompat.getDrawable(getResources(), R.drawable.ic_menu_notifications_active, null), num); 545 | } else { 546 | // Hide the badge and show the washed-out button 547 | ActionItemBadge.update(mNotificationButton, ResourcesCompat.getDrawable(getResources(), R.drawable.ic_menu_notifications, null), Integer.MIN_VALUE); 548 | } 549 | } 550 | 551 | public void setMessagesNum(int num) { 552 | // Only update message count if enabled 553 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_MESSAGING, false)) { 554 | if (num > 0) { 555 | ActionItemBadge.update(mNavigationView.getMenu().findItem(R.id.nav_messages), num); 556 | } else { 557 | // Hide the badge and show the washed-out button 558 | ActionItemBadge.update(mNavigationView.getMenu().findItem(R.id.nav_messages), Integer.MIN_VALUE); 559 | } 560 | } 561 | } 562 | 563 | public void setRequestsNum(int num) { 564 | if (num > 0) { 565 | ActionItemBadge.update(mNavigationView.getMenu().findItem(R.id.nav_friendreq), num); 566 | } else { 567 | // Hide the badge and show the washed-out button 568 | ActionItemBadge.update(mNavigationView.getMenu().findItem(R.id.nav_friendreq), Integer.MIN_VALUE); 569 | } 570 | } 571 | 572 | private String chooseUrl() { 573 | // Handle intents 574 | Intent intent = getIntent(); 575 | String action = intent.getAction(); 576 | String type = intent.getType(); 577 | 578 | if (Intent.ACTION_SEND.equals(action) && type != null) { 579 | if (URLUtil.isValidUrl(intent.getStringExtra(Intent.EXTRA_TEXT))) { 580 | try { 581 | Log.v(Helpers.LogTag, "Shared URL Intent"); 582 | return "https://mbasic.facebook.com/composer/?text=" + URLEncoder.encode(intent.getStringExtra(Intent.EXTRA_TEXT), "utf-8"); 583 | } catch (UnsupportedEncodingException e) { 584 | e.printStackTrace(); 585 | } 586 | } 587 | } else if (Intent.ACTION_VIEW.equals(action) && intent.getData() != null && URLUtil.isValidUrl(intent.getData().toString())) { 588 | // If there is a intent containing a facebook link, go there 589 | Log.v(Helpers.LogTag, "Opened URL Intent"); 590 | return intent.getData().toString(); 591 | } 592 | 593 | // If nothing has happened at this point, we want the default url 594 | return FACEBOOK_URL_BASE; 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/NotificationService.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.app.IntentService; 4 | import android.app.Notification; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.SharedPreferences; 10 | import android.net.Uri; 11 | import android.os.Build; 12 | import android.preference.PreferenceManager; 13 | import android.support.v4.app.NotificationCompat; 14 | import android.support.v4.app.TaskStackBuilder; 15 | import android.support.v4.content.ContextCompat; 16 | import android.util.Log; 17 | import android.webkit.CookieManager; 18 | 19 | import org.jsoup.Jsoup; 20 | import org.jsoup.select.Elements; 21 | import org.mcsoxford.rss.RSSItem; 22 | import org.mcsoxford.rss.RSSReader; 23 | import org.mcsoxford.rss.RSSReaderException; 24 | 25 | import java.io.IOException; 26 | import java.text.DateFormat; 27 | import java.text.ParseException; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Date; 31 | import java.util.List; 32 | import java.util.Locale; 33 | 34 | public class NotificationService extends IntentService { 35 | private static final DateFormat DATEFORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH); 36 | private static final String NOTIFICATION_URL = "https://www.facebook.com/notifications"; 37 | private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 Firefox/4.0"; 38 | 39 | private Date mLastNotification = null; 40 | private String feedURI = null; 41 | private SharedPreferences mPreferences; 42 | 43 | public NotificationService() { 44 | super("NotificationService"); 45 | } 46 | 47 | @Override 48 | public void onCreate() { 49 | super.onCreate(); 50 | mPreferences = PreferenceManager.getDefaultSharedPreferences(this); 51 | 52 | // Try to find a feed uri 53 | feedURI = mPreferences.getString("feed_uri", null); 54 | Log.v(Helpers.LogTag, "Found Feed in preferences"); 55 | 56 | // Try to find the most recent notification 57 | String dateString = mPreferences.getString("last_notification_date", null); 58 | if (dateString != null) { 59 | try { 60 | mLastNotification = DATEFORMAT.parse(dateString); 61 | } catch (ParseException e) { 62 | mLastNotification = null; 63 | Log.i(Helpers.LogTag, "Last notification timestamp could parsed"); 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public void onDestroy() { 70 | super.onDestroy(); 71 | 72 | // Save the most recent notification to preferences 73 | if (mLastNotification != null) { 74 | String datetime = DATEFORMAT.format(mLastNotification); 75 | mPreferences.edit().putString("last_notification_date", datetime).apply(); 76 | } 77 | } 78 | 79 | @Override 80 | protected void onHandleIntent(Intent intent) { 81 | Log.v(Helpers.LogTag, "Notification alarm running"); 82 | 83 | // Check if we have a Feed URL 84 | if (feedURI == null) { 85 | // Try to find the Feed URL 86 | updateFeed(); 87 | } 88 | 89 | List notificationsBlob = fetchNotifications(); 90 | 91 | // If we can't get the notifications, don't waste resources 92 | if (notificationsBlob == null) { 93 | return; 94 | } 95 | 96 | if (mLastNotification != null) { 97 | List unread = new ArrayList<>(); 98 | 99 | for (RSSItem rssItem : notificationsBlob) { 100 | if (!rssItem.getPubDate().after(mLastNotification)) { 101 | // Only add notifications that have not been posted 102 | break; 103 | } 104 | unread.add(rssItem); 105 | } 106 | 107 | // Only proceed if there is a new notification 108 | if (unread.size() > 0) { 109 | // Update the last notification 110 | mLastNotification = unread.get(0).getPubDate(); 111 | 112 | // Send the unread notifications to be posted 113 | sendNotification(unread); 114 | } 115 | } else { 116 | // No previous notification, set it to the most recent notification 117 | if (notificationsBlob.size() > 0) { 118 | mLastNotification = notificationsBlob.get(0).getPubDate(); 119 | } 120 | } 121 | } 122 | 123 | private void updateFeed() { 124 | Log.i(Helpers.LogTag, "Updating Feed URL"); 125 | try { 126 | Elements element = Jsoup.connect(NOTIFICATION_URL).userAgent(DESKTOP_USERAGENT).timeout(10000) 127 | .cookie(MainActivity.FACEBOOK_URL_BASE, CookieManager.getInstance().getCookie(MainActivity.FACEBOOK_URL_BASE)).get() 128 | .select("div#content").select("div.fwn").select("a[href*=rss20]"); 129 | feedURI = "https://www.facebook.com/" + element.attr("href"); 130 | mPreferences.edit().putString("feed_uri", feedURI).apply(); 131 | Log.i(Helpers.LogTag, "Feed URL set"); 132 | } catch (IOException e) { 133 | e.printStackTrace(); 134 | Log.e(Helpers.LogTag, "Failed to find Feed URL"); 135 | } 136 | } 137 | 138 | private List fetchNotifications() { 139 | RSSReader reader = new RSSReader(); 140 | try { 141 | return reader.load(feedURI).getItems(); 142 | } catch (RSSReaderException e) { 143 | Log.e(Helpers.LogTag, "Some error occurred with the RSS Reader"); 144 | e.printStackTrace(); 145 | } catch (Exception e) { 146 | Log.e(Helpers.LogTag, "Some error occurred when attempting to get RSS result"); 147 | e.printStackTrace(); 148 | } 149 | return null; 150 | } 151 | 152 | private void sendNotification(List notifications) { 153 | NotificationCompat.Builder mBuilder = 154 | new NotificationCompat.Builder(this) 155 | .setColor(ContextCompat.getColor(this, R.color.colorPrimary)) 156 | .setSmallIcon(R.drawable.notify_logo) 157 | .setContentTitle(getString(R.string.app_name)) 158 | .setAutoCancel(true) 159 | .setDefaults(-1); 160 | 161 | // Intent depends on context 162 | Intent resultIntent; 163 | 164 | if (notifications.size() > 1) { 165 | // If there are multiple notifications, mention the number 166 | String text = getString(R.string.notification_multiple_text, notifications.size()); 167 | mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text)).setContentText(text); 168 | 169 | // Set the url to the notification centre 170 | resultIntent = new Intent(this, MainActivity.class); 171 | resultIntent.setAction(Intent.ACTION_VIEW); 172 | resultIntent.setData(Uri.parse(MainActivity.FACEBOOK_URL_BASE + "notifications/")); 173 | } else { 174 | // Set the title 175 | RSSItem notification = notifications.get(0); 176 | mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(notification.getTitle())).setContentText(notification.getTitle()); 177 | 178 | // View all Notifications button 179 | Intent viewNotificationsIntent = new Intent(this, MainActivity.class); 180 | viewNotificationsIntent.setAction(Intent.ACTION_VIEW); 181 | viewNotificationsIntent.setData(Uri.parse(MainActivity.FACEBOOK_URL_BASE + "notifications/")); 182 | PendingIntent pendingViewNotifications = PendingIntent.getActivity(getApplicationContext(), 0, viewNotificationsIntent, 0); 183 | mBuilder.addAction(R.drawable.ic_menu_notifications_active, getString(R.string.notification_viewall), pendingViewNotifications); 184 | 185 | // Creates an explicit intent for an Activity in your app 186 | resultIntent = new Intent(this, MainActivity.class); 187 | resultIntent.setAction(Intent.ACTION_VIEW); 188 | resultIntent.setData(notification.getLink()); 189 | } 190 | 191 | // Notification Priority (make LED blink) 192 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 193 | mBuilder.setPriority(Notification.PRIORITY_HIGH); 194 | } 195 | 196 | // Vibration 197 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_NOTIFICATION_VIBRATE, true)) { 198 | mBuilder.setVibrate(new long[]{500, 500}); 199 | } else { 200 | mBuilder.setVibrate(null); 201 | } 202 | 203 | // Create TaskStack to ensure correct back button behaviour 204 | TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); 205 | // Adds the back stack for the Intent (but not the Intent itself) 206 | stackBuilder.addParentStack(MainActivity.class); 207 | // Adds the Intent that starts the Activity to the top of the stack 208 | stackBuilder.addNextIntent(resultIntent); 209 | PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); 210 | mBuilder.setContentIntent(resultPendingIntent); 211 | 212 | // Build the notification 213 | Notification notification = mBuilder.build(); 214 | 215 | // Set the LED colour 216 | notification.ledARGB = ContextCompat.getColor(this, R.color.colorPrimary); 217 | 218 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 219 | // notifyID allows you to update the notification later on. 220 | int notifyID = 1; 221 | mNotificationManager.notify(notifyID, notification); 222 | 223 | Log.i(Helpers.LogTag, "Notification posted"); 224 | } 225 | } -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/PollReceiver.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.preference.PreferenceManager; 10 | import android.util.Log; 11 | 12 | public class PollReceiver extends BroadcastReceiver { 13 | static void scheduleAlarms(Context ctxt, boolean cancel) { 14 | // Prepare the intent for the notification alarm 15 | AlarmManager mgr = (AlarmManager) ctxt.getSystemService(Context.ALARM_SERVICE); 16 | Intent i = new Intent(ctxt, NotificationService.class); 17 | PendingIntent pi = PendingIntent.getService(ctxt, 0, i, 0); 18 | 19 | // Start the alarm 20 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctxt); 21 | if (preferences.getBoolean(SettingsActivity.KEY_PREF_NOTIFICATIONS_ENABLED, false) && !cancel) { 22 | int interval = Integer.parseInt(preferences.getString(SettingsActivity.KEY_PREF_NOTIFICATION_INTERVAL, "600000")); 23 | mgr.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, interval, pi); 24 | Log.v(Helpers.LogTag, "Notification repeating alarm started"); 25 | } else { 26 | // Cancel the alarm if notifications are disabled 27 | mgr.cancel(pi); 28 | Log.v(Helpers.LogTag, "Notification repeating alarm canceled"); 29 | } 30 | } 31 | 32 | @Override 33 | public void onReceive(Context ctxt, Intent i) { 34 | scheduleAlarms(ctxt, false); 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.os.Bundle; 4 | import android.preference.PreferenceFragment; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.MenuItem; 7 | 8 | public class SettingsActivity extends AppCompatActivity { 9 | public static final String KEY_PREF_BACK_BUTTON = "back_button_enabled"; 10 | public static final String KEY_PREF_STOP_IMAGES = "stop_images"; 11 | public static final String KEY_PREF_FAB_SCROLL = "hide_fab_on_scroll"; 12 | public static final String KEY_PREF_MESSAGING = "messaging_enabled"; 13 | public static final String KEY_PREF_JUMP_TOP_BUTTON = "jump_top_enabled"; 14 | public static final String KEY_PREF_LOCATION = "location_enabled"; 15 | public static final String KEY_PREF_MOST_RECENT_MENU = "most_recent_menu"; 16 | public static final String KEY_PREF_HIDE_MENU_BAR = "hide_menu_bar"; 17 | public static final String KEY_PREF_HIDE_EDITOR = "hide_editor_newsfeed"; 18 | public static final String KEY_PREF_HIDE_SPONSORED = "hide_sponsored"; 19 | public static final String KEY_PREF_HIDE_BIRTHDAYS = "hide_birthdays"; 20 | public static final String KEY_PREF_NOTIFICATIONS_ENABLED = "notifications_enabled"; 21 | public static final String KEY_PREF_NOTIFICATION_INTERVAL = "notification_interval"; 22 | public static final String KEY_PREF_NOTIFICATION_VIBRATE = "notifications_vibration"; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | assert getSupportActionBar() != null; 28 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 29 | getSupportActionBar().setTitle(R.string.menu_settings); 30 | 31 | // Display the fragment as the main content. 32 | getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit(); 33 | } 34 | 35 | @Override 36 | public boolean onOptionsItemSelected(MenuItem item) { 37 | switch (item.getItemId()) { 38 | case android.R.id.home: 39 | finish(); 40 | return true; 41 | default: 42 | return super.onOptionsItemSelected(item); 43 | } 44 | } 45 | 46 | public static class SettingsFragment extends PreferenceFragment { 47 | @Override 48 | public void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | 51 | // Load the preferences from an XML resource 52 | addPreferencesFromResource(R.xml.settings); 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/java/me/jakelane/wrapperforfacebook/WebViewListener.java: -------------------------------------------------------------------------------- 1 | package me.jakelane.wrapperforfacebook; 2 | 3 | import android.Manifest; 4 | import android.app.DownloadManager; 5 | import android.app.PendingIntent; 6 | import android.content.ClipData; 7 | import android.content.ClipboardManager; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.SharedPreferences; 11 | import android.graphics.Bitmap; 12 | import android.graphics.drawable.Drawable; 13 | import android.net.Uri; 14 | import android.net.UrlQuerySanitizer; 15 | import android.os.Environment; 16 | import android.preference.PreferenceManager; 17 | import android.provider.MediaStore; 18 | import android.support.customtabs.CustomTabsIntent; 19 | import android.support.design.widget.Snackbar; 20 | import android.support.v4.content.ContextCompat; 21 | import android.util.Log; 22 | import android.view.ContextMenu; 23 | import android.view.MenuItem; 24 | import android.view.View; 25 | import android.webkit.WebView; 26 | 27 | import com.github.clans.fab.FloatingActionMenu; 28 | import com.greysonparrelli.permiso.Permiso; 29 | import com.squareup.picasso.Picasso; 30 | import com.squareup.picasso.Target; 31 | 32 | import java.io.File; 33 | 34 | import im.delight.android.webview.AdvancedWebView; 35 | 36 | class WebViewListener implements AdvancedWebView.Listener { 37 | private static final int ID_SAVE_IMAGE = 0; 38 | private static final int ID_SHARE_IMAGE = 1; 39 | private static final int ID_COPY_IMAGE_LINK = 2; 40 | private static final int ID_SHARE_LINK = 3; 41 | private static final int ID_COPY_LINK = 4; 42 | 43 | // *{-webkit-tap-highlight-color: rgba(0,0,0, 0.0);outline: none;} 44 | private static final String HIDE_ORANGE_FOCUS = "*%7B-webkit-tap-highlight-color%3Atransparent%3Boutline%3A0%7D"; 45 | // #page{top:-45px;} 46 | private static final String HIDE_MENU_BAR_CSS = "%23page%7Btop%3A-45px%7D"; 47 | // #mbasic_inline_feed_composer{display:none} 48 | private static final String HIDE_COMPOSER_CSS = "%23mbasic_inline_feed_composer%7Bdisplay%3Anone%7D"; 49 | // article[data-ft*=ei]{display:none;} 50 | private static final String HIDE_SPONSORED = "article%5Bdata-ft*%3Dei%5D%7Bdisplay%3Anone%7D"; 51 | // article#u_1j_4{display:none;} 52 | private static final String HIDE_BIRTHDAYS = "article%23u_1j_4%7Bdisplay%3Anone%3B%7D"; 53 | // ._59e9._55wr._4g33._400s{display:none} 54 | private static final String HIDE_TOP_STORIES_BUTTON = "._59e9._55wr._4g33._400s%7Bdisplay%3Anone%7D"; 55 | 56 | private final MainActivity mActivity; 57 | private final SharedPreferences mPreferences; 58 | private final AdvancedWebView mWebView; 59 | private final FloatingActionMenu mMenuFAB; 60 | private final DownloadManager mDownloadManager; 61 | 62 | private final int mScrollThreshold; 63 | private final View mCoordinatorLayoutView; 64 | 65 | WebViewListener(MainActivity activity, WebView view) { 66 | mActivity = activity; 67 | mCoordinatorLayoutView = activity.mCoordinatorLayoutView; 68 | mWebView = (AdvancedWebView) view; 69 | mPreferences = PreferenceManager.getDefaultSharedPreferences(activity); 70 | mScrollThreshold = activity.getResources().getDimensionPixelOffset(R.dimen.fab_scroll_threshold); 71 | mMenuFAB = (FloatingActionMenu) activity.findViewById(R.id.menuFAB); 72 | mDownloadManager = (DownloadManager) mActivity.getSystemService(Context.DOWNLOAD_SERVICE); 73 | } 74 | 75 | @Override 76 | public void onPageStarted(String url, Bitmap favicon) { 77 | // Show the spinner and hide the WebView 78 | mActivity.setLoading(true); 79 | } 80 | 81 | @Override 82 | public void onPageFinished(String url) { 83 | // Only do things if logged in 84 | if (mActivity.checkLoggedInState()) { 85 | // Load a certain page if there is a parameter 86 | JavaScriptHelpers.paramLoader(mWebView, url); 87 | 88 | // Hide Orange highlight on focus 89 | String css = HIDE_ORANGE_FOCUS; 90 | 91 | // Hide the menu bar (but not on the composer or if disabled) 92 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_HIDE_MENU_BAR, true) && !url.contains("/composer/") && !url.contains("/friends/")) { 93 | css += HIDE_MENU_BAR_CSS; 94 | mActivity.swipeView.setEnabled(true); 95 | } else { 96 | mActivity.swipeView.setEnabled(false); 97 | } 98 | 99 | if (url.contains("mbasic.facebook.com/composer/?text=")) { 100 | UrlQuerySanitizer sanitizer = new UrlQuerySanitizer(); 101 | sanitizer.setAllowUnregisteredParamaters(true); 102 | sanitizer.parseUrl(url); 103 | String param = sanitizer.getValue("text"); 104 | 105 | mWebView.loadUrl("javascript:(function()%7Bdocument.querySelector('%23composerInput').innerHTML%3D'" + param + "'%7D)()"); 106 | } 107 | 108 | // Hide the status editor on the News Feed if setting is enabled 109 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_HIDE_EDITOR, true)) { 110 | css += HIDE_COMPOSER_CSS; 111 | } 112 | 113 | // Hide 'Sponsored' content (ads) 114 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_HIDE_SPONSORED, true)) { 115 | css += HIDE_SPONSORED; 116 | } 117 | 118 | // Hide birthday content from News Feed 119 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_HIDE_BIRTHDAYS, true)) { 120 | css += HIDE_BIRTHDAYS; 121 | } 122 | 123 | // Hide Top Stories button on News Feed 124 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_MOST_RECENT_MENU, true)) { 125 | css += HIDE_TOP_STORIES_BUTTON; 126 | } 127 | 128 | // Inject the css 129 | JavaScriptHelpers.loadCSS(mWebView, css); 130 | 131 | // Get the currently open tab and check on the navigation menu 132 | JavaScriptHelpers.updateCurrentTab(mWebView); 133 | 134 | // Get the notification number 135 | JavaScriptHelpers.updateNumsService(mWebView); 136 | 137 | // Stop loading 138 | mActivity.setLoading(false); 139 | } 140 | } 141 | 142 | @Override 143 | public void onPageError(int errorCode, String description, String failingUrl) { 144 | mActivity.setLoading(false); 145 | } 146 | 147 | @Override 148 | public void onDownloadRequested(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { 149 | } 150 | 151 | @Override 152 | public void onExternalPageRequest(String url) { 153 | Log.i(Helpers.LogTag, "External page: " + url); 154 | 155 | // Launch another Activity that handles URLs 156 | CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder(); 157 | intentBuilder.setShowTitle(true); 158 | intentBuilder.setToolbarColor(ContextCompat.getColor(mActivity, R.color.colorPrimary)); 159 | 160 | Intent actionIntent = new Intent(Intent.ACTION_SEND); 161 | actionIntent.setType("text/plain"); 162 | actionIntent.putExtra(Intent.EXTRA_TEXT, url); 163 | 164 | PendingIntent menuItemPendingIntent = PendingIntent.getActivity(mActivity, 0, actionIntent, 0); 165 | intentBuilder.addMenuItem(mActivity.getString(R.string.share_text), menuItemPendingIntent); 166 | try { 167 | intentBuilder.build().launchUrl(mActivity, Uri.parse(url)); 168 | } catch (android.content.ActivityNotFoundException ex) { 169 | Log.e(Helpers.LogTag, "Could not launch url, activity was not found"); 170 | } 171 | } 172 | 173 | @Override 174 | public void onScrollChange(int scrollX, int scrollY, int oldScrollX, int oldScrollY) { 175 | // Make sure the hiding is enabled and the scroll was significant 176 | if (mPreferences.getBoolean(SettingsActivity.KEY_PREF_FAB_SCROLL, false) && Math.abs(oldScrollY - scrollY) > mScrollThreshold) { 177 | if (scrollY > oldScrollY) { 178 | // User scrolled down, hide the button 179 | mMenuFAB.hideMenuButton(true); 180 | } else if (scrollY < oldScrollY) { 181 | // User scrolled up, show the button 182 | mMenuFAB.showMenuButton(true); 183 | } 184 | } 185 | } 186 | 187 | @Override 188 | public void onCreateContextMenu(ContextMenu contextMenu) { 189 | final WebView.HitTestResult result = mWebView.getHitTestResult(); 190 | 191 | MenuItem.OnMenuItemClickListener handler = new MenuItem.OnMenuItemClickListener() { 192 | public boolean onMenuItemClick(MenuItem item) { 193 | int i = item.getItemId(); 194 | if (i == ID_SAVE_IMAGE) { 195 | Permiso.getInstance().requestPermissions(new Permiso.IOnPermissionResult() { 196 | @Override 197 | public void onPermissionResult(Permiso.ResultSet resultSet) { 198 | if (resultSet.areAllPermissionsGranted()) { 199 | // Save the image 200 | Uri uri = Uri.parse(result.getExtra()); 201 | DownloadManager.Request request = new DownloadManager.Request(uri); 202 | 203 | // Set the download directory 204 | File downloads_dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 205 | if (!downloads_dir.exists()) { 206 | if (!downloads_dir.mkdirs()) { 207 | return; 208 | } 209 | } 210 | File destinationFile = new File(downloads_dir, uri.getLastPathSegment()); 211 | request.setDestinationUri(Uri.fromFile(destinationFile)); 212 | 213 | // Make notification stay after download 214 | request.setVisibleInDownloadsUi(true); 215 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 216 | 217 | // Start the download 218 | mDownloadManager.enqueue(request); 219 | } else { 220 | Snackbar.make(mCoordinatorLayoutView, R.string.permission_denied, Snackbar.LENGTH_SHORT).show(); 221 | } 222 | } 223 | 224 | @Override 225 | public void onRationaleRequested(Permiso.IOnRationaleProvided callback, String... permissions) { 226 | // TODO Permiso.getInstance().showRationaleInDialog("Title", "Message", null, callback); 227 | callback.onRationaleProvided(); 228 | } 229 | }, Manifest.permission.WRITE_EXTERNAL_STORAGE); 230 | return true; 231 | } else if (i == ID_SHARE_IMAGE) { 232 | final Uri uri = Uri.parse(result.getExtra()); 233 | // Share image 234 | Target target = new Target() { 235 | @Override 236 | public void onPrepareLoad(Drawable placeHolderDrawable) { 237 | } 238 | 239 | @Override 240 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom arg1) { 241 | String path = MediaStore.Images.Media.insertImage(mActivity.getContentResolver(), bitmap, uri.getLastPathSegment(), null); 242 | 243 | Intent shareIntent = new Intent(Intent.ACTION_SEND); 244 | shareIntent.setType("image/*"); 245 | shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(path)); 246 | mActivity.startActivity(Intent.createChooser(shareIntent, mActivity.getString(R.string.context_share_image))); 247 | } 248 | 249 | @Override 250 | public void onBitmapFailed(Drawable errorDrawable) { 251 | } 252 | }; 253 | 254 | Picasso.with(mActivity).load(uri).into(target); 255 | Snackbar.make(mCoordinatorLayoutView, R.string.context_share_image_progress, Snackbar.LENGTH_SHORT).show(); 256 | return true; 257 | } else if (i == ID_COPY_IMAGE_LINK || i == ID_COPY_LINK) { 258 | // Copy the image link to the clipboard 259 | ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE); 260 | ClipData clip = ClipData.newUri(mActivity.getContentResolver(), "URI", Uri.parse(result.getExtra())); 261 | clipboard.setPrimaryClip(clip); 262 | Snackbar.make(mCoordinatorLayoutView, R.string.content_copy_link_done, Snackbar.LENGTH_LONG).show(); 263 | return true; 264 | } else if (i == ID_SHARE_LINK) { 265 | // Share the link 266 | Intent shareIntent = new Intent(Intent.ACTION_SEND); 267 | shareIntent.setType("text/plain"); 268 | shareIntent.putExtra(Intent.EXTRA_TEXT, result.getExtra()); 269 | mActivity.startActivity(Intent.createChooser(shareIntent, mActivity.getString(R.string.context_share_link))); 270 | return true; 271 | } else { 272 | return false; 273 | } 274 | } 275 | }; 276 | 277 | // Long pressed image 278 | if (result.getType() == WebView.HitTestResult.IMAGE_TYPE || result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { 279 | contextMenu.add(0, ID_SAVE_IMAGE, 0, R.string.context_save_image).setOnMenuItemClickListener(handler); 280 | contextMenu.add(0, ID_SHARE_IMAGE, 0, R.string.context_share_image).setOnMenuItemClickListener(handler); 281 | contextMenu.add(0, ID_COPY_IMAGE_LINK, 0, R.string.context_copy_image_link).setOnMenuItemClickListener(handler); 282 | } 283 | 284 | // Long pressed link 285 | if (result.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { 286 | contextMenu.add(0, ID_SHARE_LINK, 0, R.string.context_share_link).setOnMenuItemClickListener(handler); 287 | contextMenu.add(0, ID_COPY_LINK, 0, R.string.context_copy_link).setOnMenuItemClickListener(handler); 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fab_checkin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fab_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fab_photo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fab_text.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_fblogin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_forward.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_friendreq.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_jump_top.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_mainmenu.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_messages.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_most_recent.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_news.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_notifications.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_notifications_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu_top_stories.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/notify_logo.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_profile.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 37 | 44 | 45 | 52 | 53 | 60 | 61 | 62 | 63 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu_badge_full.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/video_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 34 | 35 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLane/Toffeed/700f63e6f04265ae6d09fcf6c5174d5f86410429/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLane/Toffeed/700f63e6f04265ae6d09fcf6c5174d5f86410429/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLane/Toffeed/700f63e6f04265ae6d09fcf6c5174d5f86410429/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLane/Toffeed/700f63e6f04265ae6d09fcf6c5174d5f86410429/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLane/Toffeed/700f63e6f04265ae6d09fcf6c5174d5f86410429/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Toffeed 4 | 5 | Abrir cajón de navegación 6 | Cerrar cajón de navegación 7 | 8 | Usuario desconocido 9 | Imágen de perfil 10 | 11 | Notificaciones 12 | Noticias 13 | Solicitudes de amistad 14 | Mensajes 15 | Búsqueda 16 | Menú Principal 17 | 18 | Entrar a Facebook 19 | Ir a arriba 20 | Recargar 21 | Atrás 22 | Adelante 23 | Opciones 24 | 25 | Publicar tu estado 26 | Publicar una foto 27 | Registrar visita 28 | Guardar imágen 29 | 30 | Compartir imágen 31 | Copiar enlace de imágen 32 | Compartir enlace 33 | Copiar enlace 34 | 35 | Compartiendo imágen… 36 | Enlace copiado al portapapeles 37 | 38 | Notificaciones 39 | 40 | Notificaciones en segundo plano 41 | Al activarlo, recibirás las notificaciones de Facebook. 42 | 43 | Intervalo de notificaciones en segundo plano 44 | Especifica el intervalo de actualización del servicio de notificación. 45 | 46 | 5 minutos 47 | 10 minutos 48 | 15 minutos 49 | 30 minutos 50 | 1 hora 51 | 2 horas 52 | 53 | 54 | 300000 55 | 600000 56 | 900000 57 | 1800000 58 | 3600000 59 | 7200000 60 | 61 | 62 | Vibrar 63 | Al activarlo, las notificaciones harán vibrar tu dispositivo. 64 | 65 | Contenido 66 | 67 | Acceso a tu ubicación 68 | Activa esto para permitir a Facebook obtener tu ubicación. 69 | 70 | Ocultar editor de estado en las noticias 71 | Al activarlo, el editor de estado no se verá en las noticias. 72 | Recargar para activar los cambios 73 | 74 | No cargar imágenes 75 | Al activarlo, no se descargarán imágenes. Algunas imágenes podrian permanecer en la caché. 76 | 77 | No mostrar contenido "patrocinado" 78 | Al activarlo, se ocultará el contenido "patrocinado"(anuncios) de la noticias. 79 | 80 | Navegación 81 | 82 | Ocultar el botón flotante de acción al deslizar 83 | Ocultar el botón cuando se desliza hacia abajo 84 | 85 | Mensajes 86 | Al activarlo, tendrás acceso a los mensajes de Facebook. Desactívalo si usas otra app de mensajeria. 87 | 88 | Botón atrás 89 | Al activarlo, el boton atrás será visible en el cajón. 90 | 91 | Botón ir a arriba 92 | Al activarlo, El botón ir a arriba será visible en el cajón. 93 | 94 | Entrar 95 | Compartir 96 | 97 | No has iniciado la sesión 98 | Algo fue mal, intenta iniciar la sesión de nuevo 99 | Algo fue super mal 100 | No se puede acceder a Facebook 101 | Algo funcionó mal en Facebook 102 | 103 | Cargando 104 | %s nuevas notificaciones. 105 | Ver todas las notificaciones 106 | 107 | Permiso denegado 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Toffeed 4 | 5 | Apri navigation drawer 6 | Chiudi navigation drawer 7 | 8 | Utente sconosciuto 9 | Foto Profilo 10 | 11 | Notifiche 12 | News Feed 13 | Richieste di Amicizia 14 | Messaggi 15 | Cerca 16 | Menu Principale 17 | 18 | Entra in Facebook 19 | Torna in Alto 20 | Ricarica 21 | Indietro 22 | Avanti 23 | Impostazioni 24 | 25 | Condividi uno stato 26 | Condividi una foto 27 | Condividi posizione 28 | 29 | Salva immagine 30 | Condividi immagine 31 | Copia link immagine 32 | Condividi link 33 | Copia link 34 | Link copiato negli appunti 35 | 36 | Condividendo immagine… 37 | 38 | Notifiche 39 | 40 | Notifiche in Background 41 | Quando attiva, riceverai notifiche in background da Toffeed. 42 | 43 | Intervallo di notifiche in background 44 | Specifica l\'intervallo di aggiornamento per il servizio di notifiche in background. 45 | 46 | 5 minuti 47 | 10 minuti 48 | 15 minuti 49 | 30 minuti 50 | 1 ora 51 | 2 ore 52 | 53 | 54 | 300000 55 | 600000 56 | 900000 57 | 1800000 58 | 3600000 59 | 7200000 60 | 61 | 62 | Vibrazione 63 | Quando attiva, le notifiche faranno vibrare il tuo device. 64 | 65 | Contenuto 66 | 67 | Accesso alla posizione 68 | Attivala per permettere a Toffeed di ottenere la tua posizione. 69 | 70 | Nascondi l\'editor di stato nel News Feed 71 | Quando attiva, l\'editor di stato sarà nascosto dal News Feed. 72 | Ricarica per applicare le modifiche 73 | 74 | Non visualizzare le immagini 75 | Quando attiva, non verranno visualizzate le immagini. Alcune immagini potrebbero rimanere nella cache. 76 | 77 | Nascondi Contenuti "Sponsorizzati" 78 | Quando attiva, verrà nascosta la pubblicità nel News Feed. 79 | 80 | Navigazione 81 | 82 | Nascondi il Floating Action Button (FAB) quando scrolli 83 | Quando attiva, nasconde il FAB quando scrolli in basso 84 | 85 | Messaggistica 86 | Quando attiva, permetterà di accedere ai Messaggi di Facebook. Disattiva questa opzione se hai un\'app di messaggistica (Facebook Messenger). 87 | 88 | Tasto "Indietro" 89 | Quando attiva, il tasto "Indietro" sarà visibile nel menu laterale. 90 | 91 | Tasto "Torna in Alto" 92 | Quando attiva, il tasto "Torna in Alto" sarà visibile nel menu laterale. 93 | 94 | Entra 95 | Condividi 96 | 97 | Non hai effettuato l\'accesso 98 | Qualcosa è andato storto, prova a eseguire di nuovo l\'accesso 99 | Qualcosa è andato molto storto 100 | Impossibile accedere a Facebook 101 | Qualcosa è andato storto alla fine di Facebook 102 | 103 | Caricamento 104 | %s nuove notifiche. 105 | Mostra tutte le Notifiche 106 | 107 | Permesso negato 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Toffeed 4 | 5 | Abrir gaveta de navegação 6 | Fechar gaveta de navegação 7 | 8 | Usuário desconhecido 9 | Foto do perfil 10 | 11 | Notificações 12 | Feed de Notícias 13 | Solicitações de Amizade 14 | Mensagens 15 | Pesquisar 16 | Menu Principal 17 | 18 | Login no Facebook 19 | Ir para o Topo 20 | Recarregar 21 | Voltar 22 | Avançar 23 | Configurações 24 | 25 | Postar um status 26 | Postar uma foto 27 | Check-in 28 | 29 | Salvar imagem 30 | Compartilhar imagem 31 | Copiar link da imagem 32 | Compartilhar link 33 | Copiar link 34 | 35 | Compartilhando imagem… 36 | Link copiado para área de transferência 37 | 38 | Notificações 39 | 40 | Notificações em segundo plano 41 | Quando ativado, você receberá notificações em segundo plano do Facebook. 42 | 43 | Intervalo de Notificações em segundo plano 44 | Especifique o intervalo de atualização para o serviço de notificação. 45 | 46 | 5 minutos 47 | 10 minutos 48 | 15 minutos 49 | 30 minutos 50 | 1 hora 51 | 2 horas 52 | 53 | 54 | 300000 55 | 600000 56 | 900000 57 | 1800000 58 | 3600000 59 | 7200000 60 | 61 | 62 | Vibrar 63 | Quando ativado, as notificações farão com que o seu dispositivo vibre. 64 | 65 | Conteúdo 66 | 67 | Acesso à localização 68 | Ative isso para permitir que o Facebook obtenha a sua localização. 69 | 70 | Esconder o editor de Status no Feed de Notícias 71 | Quando ativado, o editor de Status será escondido no Feed de Notícias. 72 | Atualizar para que as alterações entrem em vigor 73 | 74 | Não carregar imagens 75 | Quando ativado, não vai baixar imagens. Algumas imagens podem permanecer no cache. 76 | 77 | Esconder Conteúdo Patrocinado 78 | Quando ativado, irá esconder Conteúdo "Patrocinado" (anúncios) do Feed de notícias. 79 | 80 | Navegação 81 | 82 | Esconder Botão de Ação Flutuante (FAB) durante a navegação 83 | Esconder o FAB quando estiver navegando para baixo 84 | 85 | Mensagens 86 | Quando ativado, irá proporcionar o acesso as Mensagens do Facebook. Desative se você tiver um aplicativo de mensagens. 87 | 88 | Botão "Voltar" 89 | Quando ativado, o botão de voltar ficará visível. 90 | 91 | Botão "Ir para o Topo" 92 | Quando ativado, o botão "Ir para o Topo" ficará visível. 93 | 94 | Login 95 | Compartilhar 96 | 97 | Você não está logado 98 | Algo deu errado, por favor tente fazer login novamente 99 | Algo deu muito errado 100 | Não foi possível acessar o Facebook 101 | Algo deu errado no fim do Facebook 102 | 103 | Carregando 104 | %s novas notificações. 105 | Ver todas as Notificações 106 | 107 | Permissão negada, ação não pode ser completada 108 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Toffeed 4 | 5 | 打开侧边栏 6 | 关闭侧边栏 7 | 8 | 未知用户 9 | 个人头像 10 | 11 | 通知 12 | 动态消息 13 | 交友邀请 14 | 讯息 15 | 搜寻 16 | 主选单 17 | 18 | 登入Facebook 19 | 回到顶部 20 | 重新整理 21 | 返回 22 | 前进 23 | 设定 24 | 25 | 近况更新 26 | 新增相片 27 | 打卡 28 | 29 | 下载图片 30 | 分享图片 31 | 复制图片网址 32 | 分享连结 33 | 复制连结网址 34 | 35 | 图片下载中… 36 | 网址已复制到剪贴簿 37 | 38 | 通知 39 | 40 | 后台通知 41 | 开启后,你将可以收到Facebook的后台通知。 42 | 43 | 后台通知时间间隔 44 | 设定「后台通知」的更新频率。 45 | 46 | 5 分钟 47 | 10 分钟 48 | 15 分钟 49 | 30 分钟 50 | 1 小时 51 | 2 小时 52 | 53 | 54 | 300000 55 | 600000 56 | 900000 57 | 1800000 58 | 3600000 59 | 7200000 60 | 61 | 62 | 震动提醒 63 | 开启后,收到后台通知手机会发出震动提醒。 64 | 65 | 内容 66 | 67 | GPS定位 68 | 开启后,将允许Facebook使用GPS定位功能。 (打卡很方便) 69 | 70 | 隐藏Facebook预设的文字编辑区 71 | 这项功能开启后,Facebook预设的文字编辑区将会被隐藏,但是请放心,Toffeed内建的更好用。 72 | 重新整理让刚刚的设定生效 73 | 74 | 不要载入任何图片 75 | 这项功能开启后,系统将不会下载任何图片。 (部分图片有可能会留存在快取中,这是正常现象。) 76 | 77 | 隐藏「赞助」的内容 78 | 这项功能开启后,系统将会隐藏你涂鸦墙上显示「赞助」的内容(也就是俗称的挡广告)。 79 | 80 | 导航 81 | 82 | 滑动时,隐藏悬浮操作按钮 83 | 当你往下滑动阅读时,隐藏右下角的悬浮操作按钮,避免挡到部分内容。 84 | 85 | 讯息功能 86 | 启动这项功能后,「讯息」按钮将会出现在左侧选单当中,方便你直接阅读讯息。如果你想继续使用脸书的「Messenger」软体,可以不要启动这项功能。 87 | 88 | 返回按钮 89 | 启动这项功能后,「返回」按钮将会出现在左侧选单当中。 90 | 91 | 回到顶部按钮 92 | 启动这项功能后,「回到顶部」按钮将会出现在左侧选单当中。 93 | 94 | 登入 95 | 分享 96 | 97 | 您尚未登入 98 | 出了点状况,请您重新登入一次 99 | 哎呀,出了大麻烦 100 | 目前无法连线到Facebook 101 | 脸书的伺服器出了些问题 102 | 103 | 读取中 104 | %s 新通知 105 | 查看全部通知 106 | 107 | 没有权限 108 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Toffeed 4 | 5 | 打開側邊選單 6 | 關閉側邊選單 7 | 8 | 未知用戶 9 | 個人頭像 10 | 11 | 通知 12 | 動態消息 13 | 交友邀請 14 | 訊息 15 | 搜尋 16 | 主選單 17 | 18 | 登入Facebook 19 | 回到頂部 20 | 重新整理 21 | 返回 22 | 前進 23 | 設定 24 | 25 | 近況更新 26 | 新增相片 27 | 打卡 28 | 29 | 下載圖片 30 | 分享圖片 31 | 複製圖片網址 32 | 分享連結 33 | 複製連結網址 34 | 35 | 圖片下載中… 36 | 網址已複製到剪貼簿 37 | 38 | 通知 39 | 40 | 後台通知 41 | 開啟後,你將可以收到Facebook的後台通知。 42 | 43 | 後台通知時間間隔 44 | 設定「後台通知」的更新頻率。 45 | 46 | 5 分鐘 47 | 10 分鐘 48 | 15 分鐘 49 | 30 分鐘 50 | 1 小時 51 | 2 小時 52 | 53 | 54 | 300000 55 | 600000 56 | 900000 57 | 1800000 58 | 3600000 59 | 7200000 60 | 61 | 62 | 震動提醒 63 | 開啟後,收到後台通知手機會發出震動提醒。 64 | 65 | 內容 66 | 67 | GPS定位 68 | 開啟後,將允許Facebook使用GPS定位功能。(打卡很方便) 69 | 70 | 隱藏Facebook預設的文字編輯區 71 | 這項功能開啟後,Facebook預設的文字編輯區將會被隱藏,但是請放心,Toffeed內建的更好用。 72 | 重新整理讓剛剛的設定生效 73 | 74 | 不要載入任何圖片 75 | 這項功能開啟後,系統將不會下載任何圖片。(部分圖片有可能會留存在快取中,這是正常現象。) 76 | 77 | 隱藏「贊助」的內容 78 | 這項功能開啟後,系統將會隱藏你塗鴉牆上顯示「贊助」的內容(也就是俗稱的擋廣告)。 79 | 80 | 導航 81 | 82 | 滑動時,隱藏懸浮操作按鈕 83 | 當你往下滑動閱讀時,隱藏右下角的懸浮操作按鈕,避免擋到部分內容。 84 | 85 | 訊息功能 86 | 啟動這項功能後,「訊息」按鈕將會出現在左側選單當中,方便你直接閱讀訊息。如果你想繼續使用臉書的「Messenger」軟體,可以不要啟動這項功能。 87 | 88 | 返回按鈕 89 | 啟動這項功能後,「返回」按鈕將會出現在左側選單當中。 90 | 91 | 回到頂部按鈕 92 | 啟動這項功能後,「回到頂部」按鈕將會出現在左側選單當中。 93 | 94 | 登入 95 | 分享 96 | 97 | 您尚未登入 98 | 出了點狀況,請您重新登入一次 99 | 哎呀,出了大麻煩 100 | 目前無法連線到Facebook 101 | 臉書的伺服器出了些問題 102 | 103 | 讀取中 104 | %s 新通知 105 | 查看全部通知 106 | 107 | 沒有權限 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3b5998 4 | #243c6d 5 | #FF4444 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 160dp 5 | 6 | 16dp 7 | 16dp 8 | 9 | 4dp 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Toffeed 4 | 5 | 422483854616848 6 | 7 | Open navigation drawer 8 | Close navigation drawer 9 | 10 | Unknown user 11 | Profile Picture 12 | 13 | Notifications 14 | News Feed 15 | Top stories 16 | Most Recent 17 | Friend Requests 18 | Messages 19 | Search 20 | Main Menu 21 | 22 | Login to Facebook 23 | Jump to Top 24 | Reload 25 | Back 26 | Forward 27 | Settings 28 | 29 | Post a status 30 | Post a photo 31 | Check In 32 | 33 | Save image 34 | Share image 35 | Copy image link 36 | Share link 37 | Copy link 38 | 39 | Sharing image… 40 | Link copied to clipboard 41 | 42 | Notifications 43 | 44 | Background Notifications 45 | When enabled, you will receive background notifications from Facebook. 46 | 47 | Background Notifications interval 48 | Specify the update interval for the notification service. 49 | 50 | 5 minutes 51 | 10 minutes 52 | 15 minutes 53 | 30 minutes 54 | 1 hour 55 | 2 hours 56 | 57 | 58 | 300000 59 | 600000 60 | 900000 61 | 1800000 62 | 3600000 63 | 7200000 64 | 65 | 66 | Vibrate 67 | When enabled, notifications will make your device vibrate. 68 | 69 | Content 70 | 71 | Location access 72 | Enable this to allow Facebook to obtain your location. 73 | 74 | Most Recent in Navigation Menu 75 | Enable this to show the Most Recent button in the Navigation Menu. 76 | 77 | Hide Status editor in News Feed 78 | When enabled, the status editor will be hidden on the News Feed. 79 | Reload for changes to take effect 80 | 81 | Don\'t load images 82 | When enabled, will not download images. Some images may remain in the cache. 83 | 84 | Hide "Sponsored" Content 85 | When enabled, will hide "Sponsored" Content (ads) from the News Feed. 86 | 87 | Hide Birthdays 88 | When enabled, will hide birthdays from the News Feed. 89 | 90 | Navigation 91 | 92 | Hide Menu bar 93 | When enabled, the Facebook Menu bar will be hidden. 94 | 95 | Hide Floating Action Button on scroll 96 | Hide the FAB when scrolling down 97 | 98 | Messaging 99 | When enabled, will provide access to Facebook Messages. Disable if you have a messaging app. 100 | 101 | Back button 102 | When enabled, the back button will be visible in the draw. 103 | 104 | Jump to Top button 105 | When enabled, the Jump to Top button will be visible in the draw. 106 | 107 | Login 108 | Share 109 | 110 | You are not logged in 111 | Something went wrong, please try logging in again 112 | Something went super wrong 113 | Could not access Facebook 114 | Something went wrong on Facebook\'s end 115 | 116 | Loading 117 | %s new notifications. 118 | View all Notifications 119 | 120 | Permission denied, cannot complete action 121 | 122 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |