package org.fox.epube; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.webkit.CookieManager; import android.webkit.JavascriptInterface; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ProgressBar; import android.widget.Toast; import com.github.javiersantos.appupdater.AppUpdater; import com.github.javiersantos.appupdater.enums.UpdateFrom; import com.livefront.bridge.Bridge; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import icepick.State; public class MainActivity extends AppCompatActivity implements NetworkStateReceiver.NetworkStateReceiverListener { private final String TAG = this.getClass().getSimpleName(); private final String BASE_URL = "https://fakecake.org/books"; private final String BASE_URL_DEV = "https://dev.fakecake.org/books"; public enum AppPage { PAGE_UNKNOWN, PAGE_LOGIN, PAGE_LIBRARY, PAGE_FAVORITES, PAGE_OFFLINE, PAGE_READER } private ProgressBar m_loadingBar; private WebView m_web; private Menu m_menu; private NetworkStateReceiver m_networkStateReceiver; private String m_baseUrl; @State protected boolean m_offlineMode; @State protected AppPage m_currentPage = AppPage.PAGE_UNKNOWN; @State protected String m_lastVisitedURL; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (BuildConfig.DEBUG) m_baseUrl = BASE_URL_DEV; else m_baseUrl = BASE_URL; startNetworkBroadcastReceiver(this); setContentView(R.layout.activity_main); setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); getSupportActionBar().setHomeButtonEnabled(true); m_web = findViewById(R.id.webview_main); WebSettings settings = m_web.getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setCacheMode(WebSettings.LOAD_DEFAULT); settings.setDatabaseEnabled(true); settings.setAppCachePath(getCacheDir().getAbsolutePath()); settings.setAppCacheEnabled(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { settings.setForceDark(WebSettings.FORCE_DARK_OFF); } m_loadingBar = findViewById(R.id.loading_progress); m_loadingBar.setVisibility(View.VISIBLE); m_loadingBar.setMax(100); if (BuildConfig.DEBUG) WebView.setWebContentsDebuggingEnabled(true); m_web.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int progress) { m_loadingBar.setProgress(progress); if (progress == 100) m_loadingBar.setVisibility(View.GONE); else m_loadingBar.setVisibility(View.VISIBLE); } }); m_web.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.d(TAG, "load URL=" + url); if (url.indexOf(m_baseUrl) == 0) { view.loadUrl(url); } else { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); try { startActivity(intent); } catch (Exception e) { toast(e.getMessage()); } } return true; } @Override public void onPageFinished(WebView view, String url) { CookieManager.getInstance().setAcceptCookie(true); CookieManager.getInstance().acceptCookie(); CookieManager.getInstance().flush(); Log.d(TAG, "finished loading URL:" + url); m_lastVisitedURL = url; view.getUrl(); } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { Log.e(TAG, "Error loading URL: " + failingUrl + ": " + description); toast(description); } }); //CookieManager.getInstance().setCookie(m_baseUrl, "is-epube-app=true"); m_web.addJavascriptInterface(new WebAppInterface(this), "EpubeApp"); m_web.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY); Bridge.restoreInstanceState(this, savedInstanceState); if (savedInstanceState == null || m_lastVisitedURL == null) { m_web.loadUrl(m_baseUrl + "/index.php"); } else { m_web.loadUrl(m_lastVisitedURL); } checkUpdates(); } protected void checkUpdates() { if (BuildConfig.DEBUG || BuildConfig.ENABLE_UPDATER) { new AppUpdater(this) .setUpdateFrom(UpdateFrom.JSON) .setUpdateJSON(String.format("https://srv.tt-rss.org/fdroid/updates/%1$s.json", this.getPackageName())) .start(); } } private void onPageSwitched(MainActivity.AppPage page) { m_currentPage = page; // not initialized yet if (m_menu == null) return; m_menu.setGroupVisible(R.id.menu_group_pages, false); m_menu.setGroupVisible(R.id.menu_group_pages_offline, false); m_menu.setGroupVisible(R.id.menu_group_library, false); m_menu.setGroupVisible(R.id.menu_group_favorites, false); m_menu.setGroupVisible(R.id.menu_group_offline, false); m_menu.setGroupVisible(R.id.menu_group_reader, false); getSupportActionBar().setDisplayHomeAsUpEnabled(false); Log.d(TAG, "switching to page: " + page); if (page != AppPage.PAGE_READER && page != AppPage.PAGE_UNKNOWN && page != AppPage.PAGE_LOGIN) { m_menu.setGroupVisible(R.id.menu_group_pages, !m_offlineMode); m_menu.setGroupVisible(R.id.menu_group_pages_offline, m_offlineMode); } if (page != AppPage.PAGE_READER) { getSupportActionBar().show(); getWindow().setStatusBarColor(getColor(R.color.colorPrimaryDark)); getWindow().setNavigationBarColor(getColor(android.R.color.black)); findViewById(R.id.toolbar).setBackground(new ColorDrawable(getColor(R.color.colorPrimary))); } if (page == AppPage.PAGE_FAVORITES) { m_menu.setGroupVisible(R.id.menu_group_favorites, true); getSupportActionBar().setTitle("Favorites"); } else if (page == AppPage.PAGE_LIBRARY) { m_menu.setGroupVisible(R.id.menu_group_library, true); getSupportActionBar().setTitle("The Epube"); } else if (page == AppPage.PAGE_OFFLINE) { m_menu.setGroupVisible(R.id.menu_group_offline, true); getSupportActionBar().setTitle("Downloaded"); } else if (page == AppPage.PAGE_READER) { m_menu.setGroupVisible(R.id.menu_group_reader, true); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } } @Override protected void onPause() { unregisterNetworkBroadcastReceiver(this); super.onPause(); } @Override public void onResume() { registerNetworkBroadcastReceiver(this); onOfflineModeChanged(!isNetworkAvailable()); super.onResume(); } public void toast(String msg) { Toast toast = Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT); toast.show(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_main, menu); m_menu = menu; onPageSwitched(AppPage.PAGE_UNKNOWN); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: m_web.evaluateJavascript("Reader.close();", null); return true; case R.id.library_favorites: m_web.loadUrl(m_baseUrl + "/index.php?mode=favorites"); return true; case R.id.library_all: m_web.loadUrl(m_baseUrl + "/index.php"); return true; case R.id.library_local: case R.id.library_local_offline: m_web.loadUrl(m_baseUrl + "/offline.html"); return true; case R.id.refresh_script_cache: m_web.evaluateJavascript("App.refreshCache(true);", null); return true; case R.id.favorites_download_all: m_web.evaluateJavascript("App.Offline.getAll();", null); return true; case R.id.offline_remove_all: m_web.evaluateJavascript("App.Offline.removeAll();", null); return true; case R.id.reader_search: getSupportActionBar().hide(); m_web.evaluateJavascript("$(\"#search-modal\").modal()", null); return true; case R.id.reader_toggle_fullscreen: toggleSystemUI(); return true; case R.id.reader_settings: getSupportActionBar().hide(); m_web.evaluateJavascript("$(\"#settings-modal\").modal()", null); return true; case R.id.logout: m_web.evaluateJavascript("App.logout();", null); return true; default: return super.onOptionsItemSelected(item); } } private void toggleSystemUI() { View decorView = getWindow().getDecorView(); if ((decorView.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_IMMERSIVE) == View.SYSTEM_UI_FLAG_IMMERSIVE ) { hideSystemUI(false); } else { hideSystemUI(true); } } public void hideSystemUI(boolean hide) { View decorView = getWindow().getDecorView(); if (hide) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE); } else { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } } private void onOfflineModeChanged(boolean offline) { Log.d(TAG, "offline mode changed: " + offline); m_offlineMode = offline; onPageSwitched(m_currentPage); // this would probably be annoying if network access status changes a lot // never reload if reading a book /* if (m_web != null && m_currentPage != AppPage.PAGE_READER) m_web.evaluateJavascript("window.location.reload();", null); */ // let's delegate this to webview if (m_web != null) { m_web.evaluateJavascript("Reader.onOfflineModeChanged("+ offline +");", null); } } class WebAppInterface { private Context m_context; WebAppInterface(Context context) { m_context = context; } @JavascriptInterface public void setPage(String pageString) { final AppPage page = AppPage.valueOf(pageString); runOnUiThread(new Runnable() { @Override public void run() { onPageSwitched(page); } }); } @JavascriptInterface public void setTitle(final String title) { runOnUiThread(new Runnable() { @Override public void run() { getSupportActionBar().setTitle(title); } }); } @JavascriptInterface public void toggleActionBar() { runOnUiThread(new Runnable() { @Override public void run() { if (getSupportActionBar().isShowing()) getSupportActionBar().hide(); else getSupportActionBar().show(); } }); } @JavascriptInterface public void hideSystemUI(final boolean hide) { runOnUiThread(new Runnable() { @Override public void run() { MainActivity.this.hideSystemUI(hide); } }); } @JavascriptInterface public void toggleSystemUI() { runOnUiThread(new Runnable() { @Override public void run() { MainActivity.this.toggleSystemUI(); } }); } @JavascriptInterface public void showActionBar(final boolean show) { runOnUiThread(new Runnable() { @Override public void run() { if (show) getSupportActionBar().show(); else getSupportActionBar().hide(); } }); } @JavascriptInterface @Deprecated public void setOffline(final boolean offline) { // No longer used, we don't rely on webview for offline/online switching anymore. } @JavascriptInterface public boolean isOnline() { return isNetworkAvailable(); } @JavascriptInterface public boolean isNightMode() { return MainActivity.this.isNightMode(); } @JavascriptInterface public void setStatusBarColor(final int r, final int g, final int b) { Log.d(TAG, "setStatusBarColor:" + r + " " + g + " " + b); runOnUiThread(new Runnable() { @Override public void run() { int color = Color.argb(255, r, g, b); float[] hsv = new float[3]; Color.colorToHSV(color, hsv); hsv[2] = 0.2f; color = Color.HSVToColor(hsv); findViewById(R.id.toolbar).setBackground(new ColorDrawable(color)); getWindow().setStatusBarColor(color); getWindow().setNavigationBarColor(color); } }); } } private boolean isNightMode() { int nightModeFlags = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; return nightModeFlags == Configuration.UI_MODE_NIGHT_YES; } private boolean isNetworkAvailable() { ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = manager.getActiveNetworkInfo(); return networkInfo != null && networkInfo.isConnected(); } @Override public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); Log.d(TAG, "onSaveInstanceState"); Bridge.saveInstanceState(this, out); } @Override public void onBackPressed() { if (m_web.canGoBack()) m_web.goBack(); else super.onBackPressed(); } @Override public void networkAvailable() { onOfflineModeChanged(false); } @Override public void networkUnavailable() { onOfflineModeChanged(true); } public void startNetworkBroadcastReceiver(Context context) { m_networkStateReceiver = new NetworkStateReceiver(); m_networkStateReceiver.addListener((NetworkStateReceiver.NetworkStateReceiverListener) context); registerNetworkBroadcastReceiver(context); } public void registerNetworkBroadcastReceiver(Context context) { context.registerReceiver(m_networkStateReceiver, new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION)); } public void unregisterNetworkBroadcastReceiver(Context context) { context.unregisterReceiver(m_networkStateReceiver); } }