diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt index 350cbb51a..50eb4da72 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt @@ -17,6 +17,7 @@ */ package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects +import android.content.Intent import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toUri import org.kiwix.kiwixmobile.core.R @@ -37,6 +38,7 @@ data class OpenFile(private val bookOnDisk: BookOnDisk) : activity.finish() activity.start { data = file.toUri() + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP } } } diff --git a/buildSrc/src/main/kotlin/custom/CustomApp.kt b/buildSrc/src/main/kotlin/custom/CustomApp.kt index 8146610d6..801198bf9 100644 --- a/buildSrc/src/main/kotlin/custom/CustomApp.kt +++ b/buildSrc/src/main/kotlin/custom/CustomApp.kt @@ -18,32 +18,55 @@ package custom +import org.json.simple.JSONObject import java.text.ParseException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +const val dateFormat = "YYYY-MM" + data class CustomApp( val name: String, val url: String, val enforcedLanguage: String, val displayName: String, - val versionName: String = parseVersionNameFromUrlOrUsePattern(url, "YYYY-MM") + val versionName: String, + val disableSideBar: Boolean = false, + val disableTabs: Boolean = false, + val disableReadAloud: Boolean = false ) { - val versionCode: Int = formatDate("YYDDD0").toInt() + constructor(name: String, parsedJson: JSONObject) : this( + name, + parsedJson.getAndCast("zim_url"), + parsedJson.getAndCast("enforced_lang"), + parsedJson.getAndCast("app_name"), + readVersionOrInfer(parsedJson), + parsedJson.getAndCast("disable_sidebar") ?: false, + parsedJson.getAndCast("disable_tabs") ?: false, + parsedJson.getAndCast("disable_read_aloud") ?: false + ) + + val versionCode: Int = formatCurrentDate("YYDDD0").toInt() + + companion object { + private fun readVersionOrInfer(parsedJson: JSONObject) = + parsedJson.getAndCast("version_name") + ?: versionNameFromUrl(parsedJson.getAndCast("zim_url")) + ?: formatCurrentDate() + } } -private fun parseVersionNameFromUrlOrUsePattern(url: String, pattern: String) = +private fun versionNameFromUrl(url: String) = url.substringAfterLast("_") .substringBeforeLast(".") .takeIf { try { - SimpleDateFormat(pattern, Locale.ROOT).parse(it) != null + SimpleDateFormat(dateFormat, Locale.ROOT).parse(it) != null } catch (parseException: ParseException) { false } } - ?: formatDate(pattern) -private fun formatDate(pattern: String) = +private fun formatCurrentDate(pattern: String = dateFormat) = Date().let(SimpleDateFormat(pattern, Locale.ROOT)::format) diff --git a/buildSrc/src/main/kotlin/custom/CustomApps.kt b/buildSrc/src/main/kotlin/custom/CustomApps.kt index a93a5ee03..31c7351b9 100644 --- a/buildSrc/src/main/kotlin/custom/CustomApps.kt +++ b/buildSrc/src/main/kotlin/custom/CustomApps.kt @@ -24,199 +24,39 @@ import org.json.simple.JSONObject import org.json.simple.parser.JSONParser import java.io.File +typealias ProductFlavors = NamedDomainObjectContainer + object CustomApps { - private val example = CustomApp( - name = "customexample", - url = "http://download.kiwix.org/zim/wikipedia_fr_test.zim", - enforcedLanguage = "en", - displayName = "Test Custom App" - ) - private val phet = CustomApp( - name = "phet", - url = "http://download.kiwix.org/zim/phet/phet_mul_2018-09.zim", - enforcedLanguage = "en", - displayName = "PhET" - ) - private val tunisie = CustomApp( - name = "tunisie", - url = "http://download.kiwix.org/zim/wikipedia_fr_tunisie_novid.zim", - enforcedLanguage = "fr", - versionName = "2018-07", - displayName = "Encyclopédie de la Tunisie" - ) - private val venezuela = CustomApp( - name = "venezuela", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_es_venezuela_2018-07.zim", - enforcedLanguage = "es", - displayName = "Enciclopedia de Venezuela" - ) - private val wikimed = CustomApp( - name = "wikimed", - url = "http://download.kiwix.org/zim/wikipedia_en_medicine_novid.zim", - enforcedLanguage = "en", - versionName = "2018-08", - displayName = "Medical Wikipedia" - ) - private val wikimedar = CustomApp( - name = "wikimedar", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_ar_medicine_novid_2018-08.zim", - enforcedLanguage = "ar", - displayName = "وِيكيبيديا الطبية (بلا اتصال بالانترنت)" - ) - private val wikimedde = CustomApp( - name = "wikimedde", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_de_medicine_novid_2018-10.zim", - enforcedLanguage = "de", - displayName = "Wikipedia Medizin (Offline)" - ) - private val wikimedes = CustomApp( - name = "wikimedes", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_es_medicine_novid_2018-10.zim", - enforcedLanguage = "es", - displayName = "Wikipedia Médica (Offline)" - ) - private val wikimedfa = CustomApp( - name = "wikimedfa", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_fa_medicine_novid_2018-07.zim", - enforcedLanguage = "fa", - displayName = "ویکی‌پدیای پزشکی (آفلاین)" - ) - private val wikimedfr = CustomApp( - name = "wikimedfr", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_fr_medicine_novid_2018-07.zim", - enforcedLanguage = "fr", - displayName = "Wikipédia médicale" - ) - private val wikimedja = CustomApp( - name = "wikimedja", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_ja_medicine_novid_2018-07.zim", - enforcedLanguage = "ja", - displayName = "医療ウィキペディア(オフライン)" - ) - private val wikimedmini = CustomApp( - name = "wikimedmini", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_en_medicine_nodet_2018-07.zim", - enforcedLanguage = "en", - displayName = "Offline WikiMed mini" - ) - private val wikimedor = CustomApp( - name = "wikimedor", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_or_medicine_novid_2018-07.zim", - enforcedLanguage = "or", - displayName = "ମେଡିକାଲ ଉଇକିପିଡିଆ (ଅଫଲାଇନ)" - ) - private val wikimedpt = CustomApp( - name = "wikimedpt", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_pt_medicine_2018-10.zim", - enforcedLanguage = "pt", - displayName = "Wikipédia Médica (Offline)" - ) - private val wikimedzh = CustomApp( - name = "wikimedzh", - url = "http://download.kiwix.org/zim/wikipedia/wikipedia_zh_medicine_novid_2018-07.zim", - enforcedLanguage = "zh", - displayName = "醫學維基百科(離線版)" - ) - private val wikispecies = CustomApp( - name = "wikispecies", - url = "http://download.kiwix.org/zim/wikispecies/wikispecies_en_all_novid_2018-08.zim", - enforcedLanguage = "en", - displayName = "WikiSpecies" - ) - private val wikivoyage = CustomApp( - name = "wikivoyage", - url = "http://download.kiwix.org/zim/wikivoyage/wikivoyage_en_all_novid_2018-10.zim", - enforcedLanguage = "en", - displayName = "Wikivoyage" - ) - private val wikivoyageeurope = CustomApp( - name = "wikivoyageeurope", - url = "http://download.kiwix.org/zim/wikivoyage/wikivoyage_en_europe_novid_2018-10.zim", - enforcedLanguage = "en", - displayName = "Wikivoyage European Travels" - ) - private val wikivoyagede = CustomApp( - name = "wikivoyagede", - url = "http://download.kiwix.org/zim/wikivoyage/wikivoyage_de_all_novid_2018-10.zim", - enforcedLanguage = "de", - displayName = "Wikivoyage auf Deutsch" - ) - private val all = listOf( - example, - phet, - tunisie, - venezuela, - wikimed, - wikimedar, - wikimedde, - wikimedes, - wikimedfa, - wikimedfr, - wikimedja, - wikimedmini, - wikimedor, - wikimedpt, - wikimedzh, - wikispecies, - wikivoyage, - wikivoyageeurope, - wikivoyagede - ) - fun createStatically(productFlavors: NamedDomainObjectContainer) { - productFlavors.create(all) + fun createDynamically(srcFolder: File, productFlavors: ProductFlavors) { + productFlavors.create(customApps(srcFolder)) } - fun createDynamically( - srcFolder: File, - productFlavors: NamedDomainObjectContainer - ) { - productFlavors.create( - srcFolder.walk() - .filter { it.name == "info.json" } - .map { - val parsedJson = JSONParser().parse(it.readText()) as JSONObject - createCustomAppFromJson( - it.parentFile.name, - parsedJson.getAndCast("zim_url"), - parsedJson.getAndCast("enforced_lang"), - parsedJson.getAndCast("app_name"), - parsedJson.getAndCast("version_name") - ) - }.toList() - ) + private fun customApps(srcFolder: File) = srcFolder.walk() + .filter { it.name == "info.json" } + .map { CustomApp(it.parentFile.name, JSONParser().parse(it.readText()) as JSONObject) } + .toList() +} + +fun ProductFlavors.create(customApps: List) { + customApps.forEach { customApp -> + this.create(customApp.name) { + versionName = customApp.versionName + versionCode = customApp.versionCode + applicationIdSuffix = ".kiwixcustom${customApp.name}" + buildConfigField("String", "ZIM_URL", "\"${customApp.url}\"") + buildConfigField("String", "ENFORCED_LANG", "\"${customApp.enforcedLanguage}\"") + buildConfigField("Boolean", "DISABLE_SIDEBAR", "${customApp.disableSideBar}") + buildConfigField("Boolean", "DISABLE_TABS", "${customApp.disableTabs}") + buildConfigField("Boolean", "DISABLE_READ_ALOUD", "${customApp.disableReadAloud}") + configureStrings(customApp.displayName) + } } } -fun NamedDomainObjectContainer.create(customApps: List) { - customApps.forEach(this::create) -} - -private fun createCustomAppFromJson( - name: String, - url: String, - enforcedLanguage: String, - displayName: String, - versionName: String? -) = if (versionName == null) CustomApp(name, url, enforcedLanguage, displayName) -else CustomApp(name, url, enforcedLanguage, displayName, versionName) - fun JSONObject.getAndCast(columnName: String): T = getOrDefault(columnName, null) as T -private fun NamedDomainObjectContainer.create( - customApp: CustomApp -) { - create(customApp.name) { - versionName = customApp.versionName - versionCode = customApp.versionCode - applicationIdSuffix = ".kiwixcustom${customApp.name}" - buildConfigField("String", "ZIM_URL", "\"${customApp.url}\"") - buildConfigField("String", "ENFORCED_LANG", "\"${customApp.enforcedLanguage}\"") - configureStrings(customApp.displayName) - } -} - fun ProductFlavor.configureStrings(appName: String) { resValue("string", "app_name", appName) resValue("string", "app_search_string", "Search $appName") diff --git a/core/detekt_baseline.xml b/core/detekt_baseline.xml index 87d46fb71..680cd1446 100644 --- a/core/detekt_baseline.xml +++ b/core/detekt_baseline.xml @@ -4,6 +4,7 @@ EmptyFunctionBlock:BooksOnDiskViewHolder.kt$BookOnDiskViewHolder.BookViewHolder${ } EmptyFunctionBlock:FetchDownloadMonitor.kt$FetchDownloadMonitor.<no name provided>${} + LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList<KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean ) MagicNumber:ArticleCount.kt$ArticleCount$1000.0 MagicNumber:ArticleCount.kt$ArticleCount$3 MagicNumber:CoreSplashActivity.kt$CoreSplashActivity$10 diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt index db0254c4e..a9cb6d35d 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ActivityModule.kt @@ -57,13 +57,17 @@ abstract class ActivityModule { menu: Menu, webViews: MutableList, urlIsValid: Boolean, - menuClickListener: MenuClickListener + menuClickListener: MenuClickListener, + disableReadAloud: Boolean, + disableTabs: Boolean ): MainMenu = MainMenu( activity, zimReaderContainer.zimFileReader, menu, webViews, urlIsValid, + disableReadAloud, + disableTabs, menuClickListener ) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CompatFindActionModeCallback.java b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CompatFindActionModeCallback.java index f419609e0..a31888acf 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CompatFindActionModeCallback.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CompatFindActionModeCallback.java @@ -132,12 +132,12 @@ public class CompatFindActionModeCallback throw new AssertionError("No WebView for CompatFindActionModeCallback::findAll"); } CharSequence find = editText.getText(); - if (find.length() == 0) { + if (find == null || find.length() == 0) { webView.clearMatches(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - webView.findAllAsync(null); + webView.findAllAsync(""); } else { - webView.findAll(null); + webView.findAll(""); } } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java index 8019fef23..b9293f27b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.java @@ -175,7 +175,7 @@ public abstract class CoreMainActivity extends BaseActivity @BindView(R2.id.activity_main_fullscreen_button) ImageButton exitFullscreenButton; @BindView(R2.id.activity_main_drawer_layout) - DrawerLayout drawerLayout; + protected DrawerLayout drawerLayout; @BindView(R2.id.activity_main_nav_view) NavigationView tableDrawerRightContainer; @BindView(R2.id.activity_main_content_frame) @@ -285,7 +285,7 @@ public abstract class CoreMainActivity extends BaseActivity Menu menu = mode.getMenu(); // Inflate custom menu icon. getMenuInflater().inflate(R.menu.menu_webview_action, menu); - readAloudSelection(menu); + configureWebViewSelectionHandler(menu); } super.onActionModeStarted(mode); } @@ -296,7 +296,7 @@ public abstract class CoreMainActivity extends BaseActivity super.onActionModeFinished(mode); } - private void readAloudSelection(Menu menu) { + protected void configureWebViewSelectionHandler(Menu menu) { if (menu != null) { menu.findItem(R.id.menu_speak_text) .setOnMenuItemClickListener(item -> { @@ -525,7 +525,7 @@ public abstract class CoreMainActivity extends BaseActivity ContextCompat.getDrawable(this, R.drawable.ic_round_add_white_36dp)); actionBar.setDisplayShowTitleEnabled(false); - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); bottomToolbar.setVisibility(View.GONE); contentFrame.setVisibility(View.GONE); progressBar.setVisibility(View.GONE); @@ -550,7 +550,7 @@ public abstract class CoreMainActivity extends BaseActivity actionBar.setDisplayHomeAsUpEnabled(false); actionBar.setDisplayShowTitleEnabled(true); - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); + setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); closeAllTabsButton.setImageDrawable( ContextCompat.getDrawable(this, R.drawable.ic_close_black_24dp)); startAnimation(tabSwitcherRoot, R.anim.slide_up); @@ -563,6 +563,10 @@ public abstract class CoreMainActivity extends BaseActivity } } + protected void setDrawerLockMode(int lockMode) { + drawerLayout.setDrawerLockMode(lockMode); + } + @OnClick(R2.id.bottom_toolbar_arrow_back) void goBack() { if (getCurrentWebView().canGoBack()) { @@ -1506,10 +1510,14 @@ public abstract class CoreMainActivity extends BaseActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - mainMenu = menuFactory.create(menu, webViewList, !urlIsInvalid(), this); + mainMenu = createMainMenu(menu); return true; } + @NotNull protected MainMenu createMainMenu(Menu menu) { + return menuFactory.create(menu, webViewList, !urlIsInvalid(), this, false, false); + } + protected boolean urlIsInvalid() { return getCurrentWebView().getUrl() == null; } @@ -1675,28 +1683,32 @@ public abstract class CoreMainActivity extends BaseActivity } if (handleEvent) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - builder.setPositiveButton(android.R.string.yes, (dialog, id) -> { - if (isOpenNewTabInBackground) { - newTabInBackground(url); - Snackbar.make(snackbarRoot, R.string.new_tab_snack_bar, Snackbar.LENGTH_LONG) - .setAction(getString(R.string.open), v -> { - if (webViewList.size() > 1) selectTab(webViewList.size() - 1); - }) - .setActionTextColor(getResources().getColor(R.color.white)) - .show(); - } else { - newTab(url); - } - }); - builder.setNegativeButton(android.R.string.no, null); - builder.setMessage(getString(R.string.open_in_new_tab)); - AlertDialog dialog = builder.create(); - dialog.show(); + showOpenInNewTabDialog(url); } } + protected void showOpenInNewTabDialog(String url) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setPositiveButton(android.R.string.yes, (dialog, id) -> { + if (isOpenNewTabInBackground) { + newTabInBackground(url); + Snackbar.make(snackbarRoot, R.string.new_tab_snack_bar, Snackbar.LENGTH_LONG) + .setAction(getString(R.string.open), v -> { + if (webViewList.size() > 1) selectTab(webViewList.size() - 1); + }) + .setActionTextColor(getResources().getColor(R.color.white)) + .show(); + } else { + newTab(url); + } + }); + builder.setNegativeButton(android.R.string.no, null); + builder.setMessage(getString(R.string.open_in_new_tab)); + AlertDialog dialog = builder.create(); + dialog.show(); + } + @Override public void setHomePage(View view) { getCurrentWebView().deactivateNightMode(); diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/KiwixTextToSpeech.java b/core/src/main/java/org/kiwix/kiwixmobile/core/main/KiwixTextToSpeech.java index 70352e26a..4682b51c3 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/KiwixTextToSpeech.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/KiwixTextToSpeech.java @@ -34,9 +34,11 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.kiwix.kiwixmobile.core.CoreApp; import org.kiwix.kiwixmobile.core.R; +import org.kiwix.kiwixmobile.core.extensions.ContextExtensionsKt; import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer; import org.kiwix.kiwixmobile.core.utils.LanguageUtils; @@ -133,6 +135,12 @@ public class KiwixTextToSpeech { } else { tts.setLanguage(locale); + if (getFeatures(tts, locale).contains(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED)) { + ContextExtensionsKt.toast(context, R.string.tts_lang_not_supported, + Toast.LENGTH_LONG); + return; + } + if (requestAudioFocus()) { loadURL(webView); } @@ -140,6 +148,12 @@ public class KiwixTextToSpeech { } } + @SuppressLint("NewApi") + private Set getFeatures(TextToSpeech tts, Locale locale) { + return VERSION.SDK_INT < VERSION_CODES.LOLLIPOP ? tts.getFeatures(locale) + : tts.getVoice().getFeatures(); + } + private void loadURL(WebView webView) { // We use JavaScript to get the content of the page conveniently, earlier making some // changes in the page diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt index f6f256a97..1a268b34b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainMenu.kt @@ -22,6 +22,7 @@ import android.content.res.Configuration import android.view.Menu import android.view.MenuItem import android.widget.TextView +import androidx.core.view.isVisible import org.kiwix.kiwixmobile.core.Intents.internal import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.intent @@ -42,6 +43,8 @@ class MainMenu( menu: Menu, webViews: MutableList, urlIsValid: Boolean, + disableReadAloud: Boolean = false, + disableTabs: Boolean = false, private val menuClickListener: MenuClickListener ) { @@ -50,7 +53,9 @@ class MainMenu( menu: Menu, webViews: MutableList, urlIsValid: Boolean, - menuClickListener: MenuClickListener + menuClickListener: MenuClickListener, + disableReadAloud: Boolean, + disableTabs: Boolean ): MainMenu } @@ -72,29 +77,39 @@ class MainMenu( } private val search = menu.findItem(R.id.menu_search) - private val tabSwitcher = menu.findItem(R.id.menu_tab_switcher) - private val tabSwitcherTextView = - tabSwitcher.actionView.findViewById(R.id.ic_tab_switcher_text) + private var tabSwitcher: MenuItem? = menu.findItem(R.id.menu_tab_switcher) + private var tabSwitcherTextView: TextView? = + tabSwitcher?.actionView?.findViewById(R.id.ic_tab_switcher_text) private val bookmarks = menu.findItem(R.id.menu_bookmarks_list) private val history = menu.findItem(R.id.menu_history) private val library = menu.findItem(R.id.menu_openfile) private val addNote = menu.findItem(R.id.menu_add_note) private val randomArticle = menu.findItem(R.id.menu_random_article) private val fullscreen = menu.findItem(R.id.menu_fullscreen) - private val readAloud = menu.findItem(R.id.menu_read_aloud) + private var readAloud: MenuItem? = menu.findItem(R.id.menu_read_aloud) private val hostBooks = menu.findItem(R.id.menu_host_books) private val help = menu.findItem(R.id.menu_help) private val settings = menu.findItem(R.id.menu_settings) private val supportKiwix = menu.findItem(R.id.menu_support_kiwix) init { + if (disableReadAloud) { + readAloud?.isVisible = false + readAloud = null + } + if (disableTabs) { + tabSwitcher?.isVisible = false + tabSwitcherTextView?.isVisible = false + tabSwitcher = null + tabSwitcherTextView = null + } randomArticle.setShowAsAction( if (activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) MenuItem.SHOW_AS_ACTION_IF_ROOM else MenuItem.SHOW_AS_ACTION_NEVER ) - tabSwitcher.actionView.setOnClickListener { menuClickListener.onTabMenuClicked() } + tabSwitcher?.actionView?.setOnClickListener { menuClickListener.onTabMenuClicked() } help.menuItemClickListener { activity.start() } settings.menuItemClickListener { activity.startActivityForResult( @@ -149,7 +164,7 @@ class MainMenu( } fun updateTabIcon(tabs: Int) { - tabSwitcherTextView.text = if (tabs > 99) ":D" else "$tabs" + tabSwitcherTextView?.text = if (tabs > 99) ":D" else "$tabs" } private fun navigateToSearch(zimFileReader: ZimFileReader): Boolean { @@ -164,15 +179,15 @@ class MainMenu( } fun onTextToSpeechStartedTalking() { - readAloud.setTitle(R.string.menu_read_aloud_stop) + readAloud?.setTitle(R.string.menu_read_aloud_stop) } fun onTextToSpeechStoppedTalking() { - readAloud.setTitle(R.string.menu_read_aloud) + readAloud?.setTitle(R.string.menu_read_aloud) } - private fun setVisibility(visibility: Boolean, vararg menuItems: MenuItem) { - menuItems.forEach { it.isVisible = visibility } + private fun setVisibility(visibility: Boolean, vararg menuItems: MenuItem?) { + menuItems.forEach { it?.isVisible = visibility } } fun tryExpandSearch(zimFileReader: ZimFileReader?) { @@ -182,8 +197,8 @@ class MainMenu( } } -private fun MenuItem.menuItemClickListener(function: (MenuItem) -> Unit) { - setOnMenuItemClickListener { +private fun MenuItem?.menuItemClickListener(function: (MenuItem) -> Unit) { + this?.setOnMenuItemClickListener { function.invoke(it) true } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/SearchActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/SearchActivity.kt index 5075ce0d8..85a85b68f 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/SearchActivity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/SearchActivity.kt @@ -52,8 +52,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel import org.kiwix.kiwixmobile.core.search.viewmodel.State -import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty -import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising +import org.kiwix.kiwixmobile.core.search.viewmodel.State.NoResults import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results import org.kiwix.kiwixmobile.core.utils.SimpleTextListener import javax.inject.Inject @@ -136,15 +135,19 @@ class SearchActivity : BaseActivity() { is Results -> { searchViewAnimator.setDistinctDisplayedChild(0) searchAdapter.items = state.values - searchView.setQuery(state.searchString, false) - searchInTextMenuItem.isVisible = state.searchString.isNotBlank() + render(state.searchString) } - Empty -> searchViewAnimator.setDistinctDisplayedChild(1) - Initialising -> { - // do nothing + is NoResults -> { + searchViewAnimator.setDistinctDisplayedChild(1) + render(state.searchString) } } + private fun render(searchString: String) { + searchView.setQuery(searchString, false) + searchInTextMenuItem.isEnabled = searchString.isNotBlank() + } + private fun onItemClick(it: SearchListItem) { searchViewModel.actions.offer(OnItemClick(it)) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt index 66dcc24da..81ca39e7b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModel.kt @@ -42,8 +42,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed -import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty -import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising +import org.kiwix.kiwixmobile.core.search.viewmodel.State.NoResults import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish @@ -64,7 +63,7 @@ class SearchViewModel @Inject constructor( private val searchResultGenerator: SearchResultGenerator ) : ViewModel() { - val state = MutableLiveData().apply { value = Initialising } + val state = MutableLiveData().apply { value = NoResults("") } val effects = PublishProcessor.create>() val actions = PublishProcessor.create() private val filter = BehaviorProcessor.createDefault("") @@ -108,10 +107,7 @@ class SearchViewModel @Inject constructor( } private fun searchPreviousScreenWhenStateIsValid(): Any = - when (val currentState = state.value) { - is Results -> effects.offer(SearchInPreviousScreen(currentState.searchString)) - else -> Unit - } + effects.offer(SearchInPreviousScreen(state.value!!.searchString)) private fun showDeleteDialog(longClick: OnItemLongClick) { effects.offer(ShowDeleteSearchDialog(longClick.searchListItem, actions)) @@ -144,7 +140,7 @@ class SearchViewModel @Inject constructor( Results(searchString, zimSearchResults) searchString.isEmpty() && recentSearchResults.isNotEmpty() -> Results(searchString, recentSearchResults) - else -> Empty + else -> NoResults(searchString) } private fun searchResultsFromZimReader() = filter diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/State.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/State.kt index bdcd11a9d..2546db534 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/State.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/State.kt @@ -21,7 +21,8 @@ package org.kiwix.kiwixmobile.core.search.viewmodel import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem sealed class State { - data class Results(val searchString: String, val values: List) : State() - object Empty : State() - object Initialising : State() + abstract val searchString: String + + data class Results(override val searchString: String, val values: List) : State() + data class NoResults(override val searchString: String) : State() } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/TagsView.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/TagsView.kt index 2c9ef33de..180c338fb 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/TagsView.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/zim_manager/TagsView.kt @@ -43,15 +43,20 @@ class TagsView(context: Context, attrs: AttributeSet) : ChipGroup(context, attrs fun render(tags: List) { tag_picture.selectBy(tags.isYesOrNotDefined()) tag_video.selectBy(tags.isYesOrNotDefined()) - tag_text_only.selectBy(tags.isDefinedAndNo() && tags.isDefinedAndNo()) - tag_short_text.selectBy(tags.isDefinedAndNo()) + val shortTextIsSelected = tags.isDefinedAndNo() + tag_text_only.selectBy( + tags.isDefinedAndNo() && + tags.isDefinedAndNo() && + !shortTextIsSelected + ) + tag_short_text.selectBy(shortTextIsSelected) } private inline fun List.isYesOrNotDefined() = isYes() || !isDefined() private inline fun List.isDefinedAndNo() = - !isDefined() && !isYes() + isDefined() && !isYes() private inline fun List.isYes() = filterIsInstance().getOrNull(0)?.value == YES diff --git a/core/src/main/res/layout/device_item.xml b/core/src/main/res/layout/device_item.xml index ff817837a..46890de48 100644 --- a/core/src/main/res/layout/device_item.xml +++ b/core/src/main/res/layout/device_item.xml @@ -1,8 +1,7 @@ + android:layout_width="match_parent" + android:layout_height="wrap_content"> + app:progressColor="@color/blue400" /> diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt index b8abd5126..7f51f9756 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt @@ -47,8 +47,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed -import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty -import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising +import org.kiwix.kiwixmobile.core.search.viewmodel.State.NoResults import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish @@ -99,7 +98,7 @@ internal class SearchViewModelTest { inner class StateTests { @Test fun `initial state is Initialising`() { - viewModel.state.test().assertValue(Initialising) + viewModel.state.test().assertValue(NoResults("")) } @Test @@ -115,13 +114,13 @@ internal class SearchViewModelTest { } @Test - fun `non empty search string with no search results is Empty`() { + fun `non empty search string with no search results is NoResults`() { emissionOf( searchTerm = "a", searchResults = emptyList(), databaseResults = listOf(RecentSearchListItem("")) ) - resultsIn(Empty) + resultsIn(NoResults("a")) } @Test @@ -136,13 +135,13 @@ internal class SearchViewModelTest { } @Test - fun `empty search string with no database results is Empty`() { + fun `empty search string with no database results is NoResults`() { emissionOf( searchTerm = "", searchResults = listOf(ZimSearchResultListItem("")), databaseResults = emptyList() ) - resultsIn(Empty) + resultsIn(NoResults("")) } @Test @@ -157,7 +156,7 @@ internal class SearchViewModelTest { viewModel.actions.offer(Filter(searchString)) viewModel.state.test() .also { testScheduler.advanceTimeBy(100, MILLISECONDS) } - .assertValueHistory(Initialising, Results(searchString, listOf(item))) + .assertValueHistory(NoResults(""), Results(searchString, listOf(item))) } @Test @@ -175,7 +174,7 @@ internal class SearchViewModelTest { ) viewModel.state.test() .also { testScheduler.advanceTimeBy(100, MILLISECONDS) } - .assertValueHistory(Initialising, Results("b", listOf(item))) + .assertValueHistory(NoResults(""), Results("b", listOf(item))) } } @@ -206,21 +205,8 @@ internal class SearchViewModelTest { } @Test - fun `ClickedSearchInText in invalid state does nothing`() { - actionResultsInEffects(ClickedSearchInText) - } - - @Test - fun `ClickedSearchInText in Result state offers SearchInPreviousScreen`() { - val item = ZimSearchResultListItem("") - val searchTerm = "searchTerm" - emissionOf( - searchTerm = searchTerm, - searchResults = listOf(item), - databaseResults = listOf(RecentSearchListItem("")) - ) - resultsIn(Results(searchTerm, listOf(item))) - actionResultsInEffects(ClickedSearchInText, SearchInPreviousScreen(searchTerm)) + fun `ClickedSearchInText offers SearchInPreviousScreen`() { + actionResultsInEffects(ClickedSearchInText, SearchInPreviousScreen("")) } @Test @@ -288,8 +274,7 @@ internal class SearchViewModelTest { searchResults: List, databaseResults: List ) { - val item = - every { searchResultGenerator.generateSearchResults(searchTerm) } returns searchResults + every { searchResultGenerator.generateSearchResults(searchTerm) } returns searchResults viewModel.actions.offer(Filter(searchTerm)) recentsFromDb.offer(databaseResults) } diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index acb1cb61e..d30e8f6ae 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -21,9 +21,6 @@ android { flavorDimensions("default") productFlavors { - - // Uncomment for static productFlavors - // CustomApps.createStatically(this) CustomApps.createDynamically(project.file("src"), this) all { File("$projectDir/src", "$name/$name.zim").let { diff --git a/custom/detekt_baseline.xml b/custom/detekt_baseline.xml index 1617bba34..58bebf995 100644 --- a/custom/detekt_baseline.xml +++ b/custom/detekt_baseline.xml @@ -4,6 +4,7 @@ LongParameterList:DownloadCustom.kt$DownloadCustom$( id: String = "", title: String = "", description: String = "", language: String = "", creator: String = "", publisher: String = "", date: String = "", url: String = "", articleCount: String = "", mediaCount: String = "", size: String = "", name: String = "", favIcon: String = "" ) MagicNumber:CustomDownloadActivity.kt$CustomDownloadActivity$3 + MagicNumber:CustomMainActivity.kt$CustomMainActivity$.25f TooGenericExceptionThrown:ActivityExtensions.kt$throw RuntimeException( """ applicationContext is ${applicationContext::class.java.simpleName} application is ${application::class.java.simpleName} """.trimIndent() ) TooManyFunctions:CustomMainActivity.kt$CustomMainActivity$CustomMainActivity diff --git a/custom/src/customexample/info.json b/custom/src/customexample/info.json index 661d4fd4a..495a11431 100644 --- a/custom/src/customexample/info.json +++ b/custom/src/customexample/info.json @@ -1,6 +1,9 @@ { "app_name": "Test Custom App", "zim_url": "http://download.kiwix.org/zim/wikipedia_fr_test.zim", - "enforced_lang": "en" + "enforced_lang": "en", + "disable_sidebar": false, + "disable_tabs": false, + "disable_read_aloud": false } diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt index 12aa2f83f..5719b513a 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt @@ -28,11 +28,14 @@ import android.os.Bundle import android.provider.Settings import android.util.Log import android.view.Menu +import android.widget.ImageView import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.drawerlayout.widget.DrawerLayout import org.kiwix.kiwixmobile.core.di.components.CoreComponent import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start import org.kiwix.kiwixmobile.core.main.CoreMainActivity +import org.kiwix.kiwixmobile.core.main.MainMenu import org.kiwix.kiwixmobile.core.main.WebViewCallback import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.utils.DialogShower @@ -72,6 +75,19 @@ class CustomMainActivity : CoreMainActivity() { return } openObbOrZim() + setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) + if (BuildConfig.DISABLE_SIDEBAR) { + val toolbarToc = findViewById(R.id.bottom_toolbar_toc) + toolbarToc.isEnabled = false + toolbarToc.alpha = .25f + } + } + + override fun setDrawerLockMode(lockMode: Int) { + super.setDrawerLockMode( + if (BuildConfig.DISABLE_SIDEBAR) DrawerLayout.LOCK_MODE_LOCKED_CLOSED + else lockMode + ) } @TargetApi(VERSION_CODES.M) @@ -152,4 +168,27 @@ class CustomMainActivity : CoreMainActivity() { override fun manageZimFiles(tab: Int) { // Do nothing } + + override fun createMainMenu(menu: Menu?): MainMenu { + return menuFactory.create( + menu!!, + webViewList, + !urlIsInvalid(), + this, + BuildConfig.DISABLE_READ_ALOUD, + BuildConfig.DISABLE_TABS + ) + } + + override fun showOpenInNewTabDialog(url: String?) { + if (BuildConfig.DISABLE_TABS) return + super.showOpenInNewTabDialog(url) + } + + override fun configureWebViewSelectionHandler(menu: Menu?) { + if (BuildConfig.DISABLE_READ_ALOUD) { + menu?.findItem(org.kiwix.kiwixmobile.core.R.id.menu_speak_text)?.isVisible = false + } + super.configureWebViewSelectionHandler(menu) + } }