diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f9e09cc3c..76262912c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -15,7 +15,7 @@ jobs: coverageReport: strategy: matrix: - api-level: [21, 21] + api-level: [21, 21, 30] fail-fast: false runs-on: macOS-latest steps: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1bea36eb9..5b5d76bce 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -10,7 +10,7 @@ jobs: instrumentation_tests: strategy: matrix: - api-level: [21, 22, 23, 24, 25, 27, 28] + api-level: [21, 22, 23, 24, 25, 27, 28, 30] fail-fast: false runs-on: macOS-latest steps: diff --git a/app/detekt_baseline.xml b/app/detekt_baseline.xml index 40e2c3c8a..2c43a15d6 100644 --- a/app/detekt_baseline.xml +++ b/app/detekt_baseline.xml @@ -11,7 +11,7 @@ MagicNumber:ShareFiles.kt$ShareFiles$24 MagicNumber:ZimManageViewModel.kt$ZimManageViewModel$5 MagicNumber:ZimManageViewModel.kt$ZimManageViewModel$500 - MagicNumber:ZimHostFragment.kt$ZimHostFragment$4 + NestedBlockDepth:LocalLibraryFragment.kt$LocalLibraryFragment$checkPermissions NestedBlockDepth:PeerGroupHandshake.kt$PeerGroupHandshake$readHandshakeAndExchangeMetaData NestedBlockDepth:ReceiverHandShake.kt$ReceiverHandShake$exchangeFileTransferMetadata PackageNaming:AvailableSpaceCalculator.kt$package org.kiwix.kiwixmobile.zim_manager.library_view diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 53648ce78..a68032508 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt index 592471fc5..e97562289 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/LocalLibraryFragment.kt @@ -20,7 +20,9 @@ package org.kiwix.kiwixmobile.nav.destination.library import android.Manifest import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle +import android.os.Environment import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -31,6 +33,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager @@ -49,9 +52,12 @@ import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.main.CoreMainActivity +import org.kiwix.kiwixmobile.core.navigateToSettings import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.REQUEST_STORAGE_PERMISSION import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower +import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BookOnDiskDelegate import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem @@ -69,6 +75,7 @@ class LocalLibraryFragment : BaseFragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil + @Inject lateinit var dialogShower: DialogShower private var actionMode: ActionMode? = null private val disposable = CompositeDisposable() @@ -202,7 +209,27 @@ class LocalLibraryFragment : BaseFragment() { REQUEST_STORAGE_PERMISSION ) } else { - requestFileSystemCheck() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Environment.isExternalStorageManager()) { + // We already have permission!! + requestFileSystemCheck() + } else { + if (sharedPreferenceUtil.manageExternalFilesPermissionDialog) { + // We should only ask for first time, If the users wants to revoke settings + // then they can directly toggle this feature from settings screen + sharedPreferenceUtil.manageExternalFilesPermissionDialog = false + // Show Dialog and Go to settings to give permission + dialogShower.show( + KiwixDialog.ManageExternalFilesPermissionDialog, + { + this.activity?.let(FragmentActivity::navigateToSettings) + } + ) + } + } + } else { + requestFileSystemCheck() + } } } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt index 76e6408a5..4453b0818 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/library/OnlineLibraryFragment.kt @@ -49,11 +49,13 @@ import org.kiwix.kiwixmobile.core.downloader.Downloader import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel +import org.kiwix.kiwixmobile.core.extensions.closeKeyboard import org.kiwix.kiwixmobile.core.extensions.snack import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.utils.BookUtils import org.kiwix.kiwixmobile.core.utils.NetworkUtils import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.core.utils.SimpleRecyclerViewScrollListener import org.kiwix.kiwixmobile.core.utils.SimpleTextListener import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog @@ -141,6 +143,13 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions { zimManageViewModel.shouldShowWifiOnlyDialog.value = false } }) + + // hides keyboard when scrolled + libraryList.addOnScrollListener(SimpleRecyclerViewScrollListener { _, newState -> + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + libraryList.closeKeyboard() + } + }) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixPrefsFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixPrefsFragment.kt index 8956f3678..948466170 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixPrefsFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixPrefsFragment.kt @@ -18,10 +18,15 @@ package org.kiwix.kiwixmobile.settings +import android.os.Build import android.os.Bundle +import android.os.Environment import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity import androidx.preference.Preference +import androidx.preference.PreferenceCategory import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.core.navigateToSettings import org.kiwix.kiwixmobile.core.settings.CorePrefsFragment import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil.Companion.PREF_STORAGE @@ -31,6 +36,7 @@ class KiwixPrefsFragment : CorePrefsFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { super.onCreatePreferences(savedInstanceState, rootKey) setUpLanguageChooser(SharedPreferenceUtil.PREF_LANG) + setMangeExternalStoragePermission() } override fun setStorage() { @@ -43,4 +49,34 @@ class KiwixPrefsFragment : CorePrefsFragment() { private fun internalStorage(): String? = ContextCompat.getExternalFilesDirs(requireContext(), null).firstOrNull()?.path + + private fun setMangeExternalStoragePermission() { + val permissionPref = findPreference(PREF_MANAGE_EXTERNAL_STORAGE_PERMISSION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + showPermissionPreference() + val externalStorageManager = Environment.isExternalStorageManager() + if (externalStorageManager) { + permissionPref!!.setSummary(org.kiwix.kiwixmobile.core.R.string.allowed) + } else { + permissionPref!!.setSummary(org.kiwix.kiwixmobile.core.R.string.not_allowed) + } + permissionPref.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + activity?.let(FragmentActivity::navigateToSettings) + true + } + } + } + + private fun showPermissionPreference() { + val preferenceCategory = findPreference( + PREF_PERMISSION + ) + preferenceCategory!!.isVisible = true + } + + companion object { + const val PREF_MANAGE_EXTERNAL_STORAGE_PERMISSION = + "pref_manage_external_storage" + } } diff --git a/app/src/main/res/drawable-night-v24/kiwix_icon.xml b/app/src/main/res/drawable-night-v24/kiwix_icon.xml new file mode 100644 index 000000000..4f41c3eba --- /dev/null +++ b/app/src/main/res/drawable-night-v24/kiwix_icon.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable-night/kiwix_icon.xml b/app/src/main/res/drawable-night/kiwix_icon.xml new file mode 100644 index 000000000..048ced6b3 --- /dev/null +++ b/app/src/main/res/drawable-night/kiwix_icon.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable-night/launch_screen.xml b/app/src/main/res/drawable-night/launch_screen.xml index 1d4d00751..1f69c6193 100644 --- a/app/src/main/res/drawable-night/launch_screen.xml +++ b/app/src/main/res/drawable-night/launch_screen.xml @@ -21,9 +21,6 @@ android:opacity="opaque"> - - - + diff --git a/app/src/main/res/drawable-v24/kiwix_icon.xml b/app/src/main/res/drawable-v24/kiwix_icon.xml new file mode 100644 index 000000000..16b1c0e76 --- /dev/null +++ b/app/src/main/res/drawable-v24/kiwix_icon.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/app/src/main/res/drawable/kiwix_icon.xml b/app/src/main/res/drawable/kiwix_icon.xml new file mode 100644 index 000000000..32fbbca20 --- /dev/null +++ b/app/src/main/res/drawable/kiwix_icon.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml index 0752c6b8e..6c38d8aa4 100644 --- a/app/src/main/res/drawable/launch_screen.xml +++ b/app/src/main/res/drawable/launch_screen.xml @@ -21,9 +21,6 @@ android:opacity="opaque"> - - - + diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/Intents.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/Intents.kt index ea6f46326..80eda6769 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/Intents.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/Intents.kt @@ -20,8 +20,20 @@ package org.kiwix.kiwixmobile.core import android.app.Activity import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.annotation.RequiresApi object Intents { @JvmStatic fun internal(clazz: Class): Intent = Intent(clazz.canonicalName).setPackage(CoreApp.instance.packageName) } +@RequiresApi(Build.VERSION_CODES.R) +fun Activity.navigateToSettings() { + val intent = Intent().apply { + action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + data = Uri.fromParts("package", packageName, null) + } + startActivity(intent) +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt index 17919d1ca..eeadaa799 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt @@ -19,6 +19,7 @@ package org.kiwix.kiwixmobile.core.extensions import android.content.Context +import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.fragment.app.Fragment @@ -41,4 +42,9 @@ fun Fragment.closeKeyboard() { inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0) } +fun View.closeKeyboard() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(windowToken, 0) +} + val Fragment.coreMainActivity get() = activity as CoreMainActivity diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt index 0b5a8aaee..375029a52 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt @@ -42,6 +42,7 @@ import kotlinx.android.synthetic.main.layout_toolbar.toolbar import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions +import org.kiwix.kiwixmobile.core.extensions.closeKeyboard import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.page.adapter.OnItemClickListener import org.kiwix.kiwixmobile.core.page.adapter.Page @@ -50,6 +51,7 @@ import org.kiwix.kiwixmobile.core.page.viewmodel.Action import org.kiwix.kiwixmobile.core.page.viewmodel.PageState import org.kiwix.kiwixmobile.core.page.viewmodel.PageViewModel import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.core.utils.SimpleRecyclerViewScrollListener import org.kiwix.kiwixmobile.core.utils.SimpleTextListener import javax.inject.Inject @@ -135,6 +137,13 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv pageViewModel.actions.offer(Action.UserClickedShowAllToggle(isChecked)) } pageViewModel.state.observe(viewLifecycleOwner, Observer(::render)) + + // hides keyboard when scrolled + recycler_view.addOnScrollListener(SimpleRecyclerViewScrollListener { _, newState -> + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + recycler_view.closeKeyboard() + } + }) } override fun onCreateView( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.java b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.java index badbf0f49..5a38303cf 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.java +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.java @@ -62,6 +62,7 @@ public abstract class CorePrefsFragment extends PreferenceFragmentCompat impleme public static final String PREF_CLEAR_ALL_HISTORY = "pref_clear_all_history"; public static final String PREF_CLEAR_ALL_NOTES = "pref_clear_all_notes"; public static final String PREF_CREDITS = "pref_credits"; + public static final String PREF_PERMISSION = "pref_permissions"; private static final int ZOOM_OFFSET = 2; private static final int ZOOM_SCALE = 25; private static final String INTERNAL_TEXT_ZOOM = "text_zoom"; @@ -174,6 +175,9 @@ public abstract class CorePrefsFragment extends PreferenceFragmentCompat impleme versionPref.setSummary(getVersionName() + " Build: " + getVersionCode()); } + + + private int getVersionCode() { try { return getActivity().getPackageManager() @@ -282,4 +286,5 @@ public abstract class CorePrefsFragment extends PreferenceFragmentCompat impleme } return Unit.INSTANCE; } + } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt index 8c7cc8ea9..19c4d0ac7 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt @@ -140,6 +140,13 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { fun updateNightMode() = nightModes.offer(nightMode) + var manageExternalFilesPermissionDialog: Boolean + get() = sharedPreferences.getBoolean(PREF_MANAGE_EXTERNAL_FILES, true) + set(prefManageExternalFilesPermissionDialog) = + sharedPreferences.edit { + putBoolean(PREF_MANAGE_EXTERNAL_FILES, prefManageExternalFilesPermissionDialog) + } + var hostedBooks: Set get() = sharedPreferences.getStringSet(PREF_HOSTED_BOOKS, null)?.toHashSet() ?: HashSet() set(hostedBooks) { @@ -172,5 +179,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { const val PREF_NIGHT_MODE = "pref_night_mode" private const val TEXT_ZOOM = "true_text_zoom" private const val DEFAULT_ZOOM = 100 + private const val PREF_MANAGE_EXTERNAL_FILES = "pref_manage_external_files" } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SimpleRecyclerViewScrollListener.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SimpleRecyclerViewScrollListener.kt new file mode 100644 index 000000000..64d5614c9 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SimpleRecyclerViewScrollListener.kt @@ -0,0 +1,34 @@ +/* + * Kiwix Android + * Copyright (c) 2021 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.utils + +import androidx.recyclerview.widget.RecyclerView + +class SimpleRecyclerViewScrollListener( + private val onLayoutScrollListener: (RecyclerView, Int) -> Unit +) : + RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + onLayoutScrollListener( + recyclerView, + newState + ) + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt index 53ce42151..7bb1139ac 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/dialog/KiwixDialog.kt @@ -84,6 +84,14 @@ sealed class KiwixDialog( cancelable = false ) + object ManageExternalFilesPermissionDialog : KiwixDialog( + R.string.all_files_permission_needed, + R.string.all_files_permission_needed_message, + R.string.yes, + R.string.no, + cancelable = false + ) + data class ShowHotspotDetails(override val args: List) : KiwixDialog( R.string.hotspot_turned_on, R.string.hotspot_details_message, diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index af11074bc..4de469255 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -68,6 +68,7 @@ Clear history Clear recent searches and tabs history Notes + Permissions All History Cleared Clear bookmarks Clear All History? @@ -255,6 +256,7 @@ Status Clears all notes on all articles Clear all notes + Allow to read and write ZIM files on SD card Change text size with 25\% increments. Pic Vid @@ -289,6 +291,10 @@ Close Drawer How to update content? To update content (a zim file) you need to download the full latest version of this very same content. You can do that via the download section. + All Files Permission Needed + In order to access all the zim files across device we need to have All Files Permission + Allowed + Not allowed @string/on @string/off diff --git a/core/src/main/res/xml/preferences.xml b/core/src/main/res/xml/preferences.xml index e8e0c52cb..081a15b34 100644 --- a/core/src/main/res/xml/preferences.xml +++ b/core/src/main/res/xml/preferences.xml @@ -95,6 +95,18 @@ android:title="@string/pref_clear_all_notes_title" app:iconSpaceReserved="false" /> + + + + +